1 Obtaining Pubmed data set

There are several code blocks that are not run in this notebook because it would take too long or take too much RAM. Consequently are instances where the computation has been done on an external sever. All the datasets generated along the way are stored in the “data” subfolder.

The data aquistion code below is not run in this notebook. It won’t take very long, but the data objects have been saved a long the way, and so for simplicity, the code it shown by the data objects are loaded in the data cleaning and analysis (which don’t take very long to run, with the exception of tSNE)

# initial query, figure out how many articles there are
# query was run on Thursday July 13th, 2017 at 17:15 PDT

query1<-'(genome AND (outbreak OR pandemic OR epidemic)) OR "genomic epidemiology"'
resQ1 <- EUtilsSummary(query1, type='esearch', db='pubmed')

resQ1 <- EUtilsSummary(query1, type='esearch', db='pubmed',retmax=resQ1@count) #9715 articles

#second query
query2<-'(genomic epidemiology OR molecular epidemiology) AND (bacteri* OR vir* OR pathogen) AND Genome' 
resQ2 <- EUtilsSummary(query2, type='esearch', db='pubmed')
resQ2 <- EUtilsSummary(query2, type='esearch', db='pubmed',retmax=resQ2@count) #

#common elements between queries
intersect(resQ1@PMID,resQ2@PMID) %>% length()
setdiff(resQ1@PMID,resQ2@PMID) %>% length()
setdiff(resQ2@PMID,resQ1@PMID) %>% length()

pmidUnique<-c(resQ2@PMID,resQ1@PMID) %>% unique() #20,702 articles

The two queries yeild slightly different datasets. It may be that because the first query is more specific and relating to outbreaks, it would not have some of the same content as the second query. Although, in playing around with multiple query options, it seems that different search terms can return different sets. For simplicity I will stick with these two.

#need to put everything together in batches, 2000 articles was the magic number before time outs occur
numVals<-seq(from=0,to=length(pmidUnique),by=2000)
numVals<-c(numVals,tail(numVals, n=1) + (length(pmidUnique) %% 2000))

genEpiData<-c()
for(i in 1:(length(numVals)-1)){
    #to make this faster, form new query on ID run in parallel
    start = numVals[i]+1
    end = numVals[i + 1]
    genEpiData <- rbind(genEpiData,formatData(pmidUnique[start:end]))
}

We can calculate the distribution of publication dates for the various articles:

yearCount<-genEpiData %>%
  group_by(YearPub) %>%
  count() %>%
  select(YearPub,n)

ggplot(genEpiData, aes(x=YearPub)) +
  geom_bar()+
  theme_bw()

Many of the articles are published in the 2000’s when next generation sequcencing was introduced. I am going to stick with articles published from 2000 onwards, and I will also find which articles may have full text available by linking to their PMC ID.

genEpiData<-genEpiData %>% filter(YearPub > 1999)

#will convert some Pubmed IDS to PMC IDs in order to grab some full texts later
#i've already read and saved this data file for an earlier analysis

idConv<-readRDS(file="../data/PMC-ids.RDS")
convItems<-idConv %>%
  filter(PMID %in% as.numeric(as.character(genEpiData$PMID))) %>%
  select(PMID,PMCID,DOI) %>%
  mutate(PMID = factor(PMID))

genEpiData<-full_join(genEpiData,convItems)
 
#save the data set
saveRDS(file = "data/genEpiData_initialQuery2.RDS",genEpiData) #about four articles could not be queried, total = 17988 articles

2 Cleaning the data

2.1 Prepare data using tidy text

Cleaning the dataset uses guidance from the tidytext online guide, it’s very useful and worth checking out. I’ve also added a few more filters and stemmed the terms using Porter’s alogorithm.

#loading the dataset as previous code was not actually run (takes too long)
genEpiData<-readRDS(file="../data/genEpiData_initialQuery2.RDS")

#commonly used words in the english language
data(stop_words)

#remove some common terms that will occur in abstract
customStopTerms<-data.frame(word=c("abstract", "text", "abstracttext","introduction","background","method","methods","methodology","conclusion","conclusions","objectives","results","result","we","materials","purpose","significance","significant","mg"))

genEpitext_df <- genEpiData[,c("PMID","Title","Abstract")] %>%
  mutate(text = paste0(Title,Abstract)) %>%
  unnest_tokens(word, text) %>%
  mutate(word = strsplit(as.character(word), "\\.")) %>% #some text is stuck together for example person.METHODS so, I am fixing that
  tidyr::unnest(word) %>% 
  anti_join(stop_words) %>%
  anti_join(customStopTerms) %>%
  filter(str_length(word)>2) %>% #only keeps words with length of 2 or greater (AMR, a useful abbreviation, is three characters long) %>%
  filter(!str_detect(word,"\\d")) %>% #get rid of any numbers
  mutate(wordStemmed = wordStem(word)) %>% #finally, get the word stems (Porter's algorithm)
  select(PMID,word,wordStemmed)

2.2 Filtering out terms that occur too frequently & too infrequently

Here I calculate the term-frequency inverse document frequency metric for each individual work in the dataset. Then I filter out words that occur very infrequently or in nearly all of the documents. I have made an arbitrary choice to not keep terms that occur in fewer than 1.0 % of documents or greater than 70% of documents. I do want coarser, as opposed to finer, clusters because my intention is to do a subset analysis within clusters once I know what they are. A word that is present in almost ~175 documents is a pretty important term, but with nearly 18,000 documents, I would prefer clusters with membership of roughly 200 or to make it meaningful for my larger goals.

#for the future, store the transformations that a single word can take on after stemming
#wordToStemmed<-genEpitext_df %>% select(word,wordStemmed) %>% unique()

# I will now also add the term frequency document frequency values.
genEpitext_df<-genEpitext_df %>%
  count(PMID, wordStemmed, sort = TRUE) %>%
  ungroup() %>%
  bind_tf_idf(wordStemmed, PMID, n)

totalArticles<-nrow(genEpiData)

wordToRemove<-genEpitext_df %>%
  group_by(wordStemmed) %>%
  count() %>%
  filter(nn < totalArticles*0.01 | nn > totalArticles*0.7)

genEpitext_df<-anti_join(genEpitext_df,wordToRemove,by="wordStemmed") #there are 1167 unique terms that meet this critiera

#saving the tidytext dataset
saveRDS(file="data/genEpiData_initialQuery2_tidyData.RDS",genEpitext_df)

2.3 Creating a bigram dataset

In this analysis, I’ve reasoned that single terms will provide more general clustering (for example, I would want all articles related to tuberculosis, or drug resistance to be a single cluster), but bigrams can be useful to resolve more fined grained structured.

 bigram_df <- genEpiData[,c("PMID","Title","Abstract")] %>%
  mutate(text = paste0(Title,Abstract)) %>%
  unnest_tokens(bigram, text,token = "ngrams",n=2) %>%
  #cleaning up each bigram
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  mutate(word1 = strsplit(as.character(word1), "\\.")) %>%
  tidyr::unnest(word1) %>%
  mutate(word2 = strsplit(as.character(word2), "\\.")) %>%
  tidyr::unnest(word2) %>% 
  filter(!(word1 %in% c(stop_words$word,customStopTerms$word))) %>%
  filter(!(word2 %in% c(stop_words$word,customStopTerms$word))) %>%
  filter(str_length(word1)>2) %>%
  filter(!str_detect(word1,"\\d")) %>% 
  filter(str_length(word2)>2) %>%
  filter(!str_detect(word2,"\\d")) %>%
  mutate(word1 = wordStem(word1)) %>%
  mutate(word2 = wordStem(word2)) %>%
  unite(bigram,word1,word2) %>% 
  select(PMID,bigram)

#calculate tf_idf
bigram_df<-bigram_df %>%
  count(PMID, bigram, sort = TRUE) %>%
  ungroup() %>%
  bind_tf_idf(bigram, PMID, n)

saveRDS(file="data/genEpiData_initialQuery2_BIGRAM_tidyData.RDS",bigram_df)

3 Analysis

Most of this analysis can be be run on a laptop with 8 GB of RAM. Keep an eye out whether or not chrome is open because it takes up a lot of memory and I’ve often had to close it an other applications to run this analysis. One expection is the initial tSNE clustering with ~18000 articles, which requires a server with more RAM to run. I’ve provided the analysis code here, but load a previously saved tSNE Clustered dataset for the remainder of the analysis.

As a note, the point of this analysis is not for the algorithms to drive everything. It’s actually for the algorithms to provide some structure that I used to make analytic decisions about or even revise. In this sense, the point is more to work together with the algorithm then default soley on my judgement’s or the methods. Places where subjective decisions have been made are clearly indicated.

3.1 Single term cluster analysis

For analysis, I will construct a document term matrix from the single term dataset, and then I will apply the tSNE algorithm to cluster data, and the HBDSCAN algorithm to derived clusters.

3.1.1 Creating the document term matrix

The document term frequency uses the tf_idf metric to assess teh distribution of terms (columns) across documents (rows). This will be provided to the tsne algorithm to cluster documents.

#load data, since RMDnote does not run the earlier steps
genEpitext_df<-readRDS(file="data/genEpiData_initialQuery2_tidyData.RDS")
#now create dtm
dtm<-genEpitext_df %>%
  select(PMID,wordStemmed,tf_idf) %>%
  spread(PMID,tf_idf)
colVals<-dtm$wordStemmed
rowVals<-colnames(dtm)[2:ncol(dtm)]
dtm<-as.matrix(dtm[,2:ncol(dtm)]) %>% t()
rownames(dtm)<-rowVals
colnames(dtm)<-colVals
dtm[is.na(dtm)]<-0
saveRDS(file="data/dtm_bigraminitialQuery2.RDS",dtm)

3.1.2 tSNE

tSNE takes a very long time to run (hours). There are two tSNE packages in R (tsne, and Rtsne) which implement slightly different versions of the tSNE algorithm (the tSNE package is akin to the initially published version). The RTsne package is considerably faster (espeically when run using nearly default parameters), in part because it allows for some inaccuraies via the theta parameter. I’ve used primarily Rtsne for analysis, with the default theta of 0.5.

I have played around with different versions of tSNE and opted to (here) only show the ones for perplexity parameter = 100 (the deault is either 30 or 50 depending on the package). Without any additional analysis, this is what the raw tsne results look like:

#load pre-run analysis on server
rtsne<-readRDS(file="data/rtsne_perplexity_100.RDS")
tsneCord<-data.frame(PMID = rownames(dtm),
                     comp1 =rtsne$Y[,1],
                     comp2 = rtsne$Y[,2])
ggplot(tsneCord,aes(x=comp1,y=comp2)) +
  geom_point(alpha=0.2)+
  labs(x="tsneComp1",
       y="tsneComp2")+
  theme_bw()
ggsave("PlainTsnePlot.pdf")
Saving 7.29 x 4.51 in image

It is possible to already see several clusters (note, the total number of documents here is 17974), with sort of one large group in the centre (still split into some clusters) and many smaller clusters to the periphery.

3.1.2.1 Cluster tSNE results

The tsne results are clustered using the HDBSCAN algorithm. The minmum number of points per cluster effects how the document clusters are assigned, so I tried a few different cluster sizes and plotted the results below. The circles indicate the boundries of the cluster, it’s noteable in each image that there is one large circle that encompasses smaller ones, this is the “noise” circle and it is coloured in blue. It is the circle, which represent true clusterings that are of interest.

set.seed(253)
minPtsVals<-c(50,75,100,125,150,250,500,1000) #trying out a bunch of different values
dfPts<-c()
for(minPts in minPtsVals){
  cl <- hdbscan(tsneCord[,2:3], minPts = minPts) #minimum cluster size of 150 documents
  dfPts<-rbind(dfPts,cbind(as.character(tsneCord$PMID),cl$cluster,rep(minPts,nrow(tsneCord))))
}
dfPts<-as.data.frame(dfPts)
colnames(dfPts)<-c("PMID","cluster","groupingLevel")
dfPts<-inner_join(dfPts,tsneCord)
dfPts$groupingLevel<-factor(dfPts$groupingLevel,c(50,75,100,125,150,250,500,1000))
dfPts %>%
  mutate(isNoise = ifelse(cluster==0,"Noise","Signal")) %>%
  ggplot(aes(x=comp1,y=comp2)) +
  geom_point(alpha=0.1)+
  theme_bw()+
  facet_wrap(~groupingLevel) +
  stat_ellipse(aes(group=cluster,col=isNoise))+
  scale_colour_manual(values=c("blue","red"))+
  theme(legend.position="None")
ggsave(file="optimalParamSearch.pdf")

As the scaling factor exceeds 150, the data group into fewer clusters until everything appears to be noise. The sweet spot is a min pts value somewhere between 50 and 150, each with rather different number of clusters:

dfPts %>%
  select(groupingLevel,cluster) %>%
  distinct() %>%
  group_by(groupingLevel) %>%
  count() %>%
  ggplot(aes(x=groupingLevel,y=n))+
  geom_bar(stat="identity")+
  ylab("Number of Clusterings")+
  xlab("minPts value")+
  geom_text(aes(label=n),nudge_y = 1,col="red")+
  theme_bw()

The final deciding factor will be the most common term in each of the clusters. If some clusters have a common term repeating across different clusters (for example, if HIV occurs across multiple clusters), than I would consider this to be too fine grained separation.

#for each grouping level, and each cluster identify the most common term
getTopTerms<-function(clustPMID,topNVal=1,clustValue = NA){
  clustValue<-clustValue[1]
  
  if(clustValue == 0)
    return("Noise")
  
  topWord<-genEpitext_df %>%
    filter(PMID %in% clustPMID) %>%
    ungroup() %>%
    group_by(wordStemmed) %>%
    tally() %>%
    arrange(-nn) %>%
    top_n(topNVal)
  
  # return character and collapse top terms (useful in even of a tie)
  topWord<-paste0(topWord$wordStemmed,collapse = "-")
  return(topWord)
}
clustTopics<-dfPts %>%
  group_by(groupingLevel,cluster) %>%
  mutate(topTerms = getTopTerms(PMID,clustValue=cluster))
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
# a small sanity check to confirm this actually worked
clustPMID<-dfPts %>% filter(groupingLevel == 50) %>% filter(cluster == 14) %>% select(PMID)
getTopTerms(clustPMID$PMID,clustValue=14)
Using `n` as weighting variable
Selecting by nn
[1] "cancer"
genEpitext_df %>%
  filter(PMID %in% clustPMID$PMID) %>%
  ungroup() %>%
  count(wordStemmed)%>%
  arrange(-nn)
# ^ yes, it actually works
#now, see if the same terms occur across different topics
clustTopics %>%
  select(topTerms,groupingLevel,cluster) %>%
  unique() %>%
  filter(groupingLevel %in% c(50,75,100,125,150)) %>%
  group_by(groupingLevel,topTerms) %>%
  count()  %>%
  ggplot(aes(x=topTerms,y=n))+
  geom_hline(yintercept=1,col="red")+
  geom_bar(stat = "identity")+
  facet_wrap(~groupingLevel,ncol=1)+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

Most top terms tend to occur only in one cluster, and tend to be infectious agents or diseases (hbv, hcv, hiv, tuberculosis). Still at lower levels of minimum cluster sizes, some clusters form for other reasons. For example, “china” and “children” are their own cluster, but neither is particularily useful to me because many different diseases can occur in china (and in fact occur in other parts of the world too), and likewise many different diseases occur in children and adults. As the cluster sizes become larger (closer to 150) I (subjectively) assess that the cluster become more “useful”, for example children collapses into other clusters. We can show this with a sankey diagram. It is easier to view this diagram with a larger monitor. It’s possible to take a closer look at top “paths” if you will of clusters as the minPts value changes - some of the largest and most consistent paths (i.e. item stays in the same cluster) are disease driven. For example an item that is classified as HIV tends to stay within the HIV cluster until minPts value gets too high. Most of the articles are just “noise”, never clustering with anything useful at all. Some positive things (again by my subject assessment) is that the less useful “china” cluster collapses into “strain”, which I think is much more useful because it is a more general classification.

library(alluvial)
tmp<-clustTopics %>%
  group_by(PMID) %>%
  summarise(clustPath = paste0(topTerms,collapse=";")) %>%
  count(clustPath) %>%
  arrange(-n)
tmp %>%
  top_n(20)
Selecting by n
tmp2<-sapply(tmp$clustPath,function(x){strsplit(x,";")}) %>% bind_rows() %>% t()
tmp2<-as.data.frame(tmp2)
colnames(tmp2)<-paste("minPts=",minPtsVals)
tmp2$freq<-tmp$n
#clean it up a bit, hide paths with fewer than 100 documents
alluvial(tmp2[,1:8],freq=tmp2$freq,hide=tmp2$freq < 100,
         cex=0.35,gap.width = 0,alpha=0.75,cex.axis=0.75,
           col = ifelse(tmp2[,1] == "Noise", "orange", "grey"))

It’s possible to see that many documents that are initially classified as noise (highlighted in the organge colour), pretty much stays noise no matter what the parameter values to change to the exception is perhaps at minPts value of 250, where everything has the term isol (for isolate). It’s also possible to see the trajector of inidividual terms that I thought were odd (for example clusters with china or children), which don’t actually cluster with any diseases, but instead eventually become clusters with generic terms like “genom” or “isolate” or “strain” and many more even just become noise. It’s possible that these are generic molecular biology type articles, so it may not be worth discarding them.

Based upon the above analysis, I’ve opted to use a minPts size of 150. I’ll use the subsequent analysis with the bigrams to help sort out cross-cutting topics and also to resurect some other pathogens that might have slipped into the “isol”, “genome”, or even “Noise” categories. So, now I will assign the official clusters to tsneCord and use tsneCord for analysis. The cluster values of 150 gives me the cleanest data, or so I have assessed, in that document cluster most clearly according to diseases or pathogens.

I will also remove those articles who are mainly classified as noise all throughout different minPtValues, they might contain something useful, but for my purposes I want to boost the signal of useable articles and so I’ve opted to remove them. This will be a total of 2659 documents (note! This doesn’t mean that the “Noise” cluster will disappear, it will still be there, but acknowledgng that those articles may have clustered usefully under different parameters, so those remain in the analysis)

veryNoiseyDocuments<-clustTopics %>%
  group_by(PMID) %>%
  summarise(clustPath = paste0(topTerms,collapse=";")) %>%
  filter(clustPath %in% c("Noise;Noise;Noise;Noise;Noise;isol;genom;Noise",
                          "Noise;Noise;Noise;Noise;Noise;isol;Noise;Noise")) %>%
  inner_join(tsneCord)
saveRDS(file="data/veryNoiseyDocuments.RDS",veryNoiseyDocuments)
tsneCord<-tsneCord %>%
  anti_join(veryNoiseyDocuments)
#get the clusters for the tsne perplexity 100 plot using HDBSCAN
cl <- hdbscan(tsneCord[,2:3], minPts = 150) #minimum cluster size of 150 documents
tsneCord$tsneCluster<-cl$cluster
#now assign the cluster topics
tsneCord<-tsneCord%>%
  group_by(tsneCluster) %>%
  mutate(tsneClusterNames = getTopTerms(PMID,clustValue=tsneCluster,topNVal = 2))

We can plot the clusters to see what their memerbship is and how they’re defined. First we’ll see how the clusters are assigned using the tsne space. I am going to remove all the articles that have been labelled as “noise” by the HDBSCAN algorithm.

#clusterNameLabel
clusterNames <- tsneCord %>%
  dplyr::group_by(tsneClusterNames) %>%
  dplyr::summarise(medX = median(comp1),
            medY = median(comp2)) %>%
  filter(tsneClusterNames != "Noise")
#write using geom_ellipse
tsneCord %>%
  filter(tsneClusterNames != "Noise") %>% 
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames)) +
  geom_point(alpha=0.2,aes(colour=tsneClusterNames))+
  theme_bw()+
  stat_ellipse(col="black",alpha=0.5)+
  geom_text(data=clusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",fontface="bold",size=3)+
  theme(legend.position="None")

From the above image, the clusters seems rather reasonbly assigned, there are some instances, but I want to break apart one big cluster : “genom-sequence”, which does make smaller clusters when minPts = 75 (from the above analysis, 75 being the closest value to 150 where the data divide genom-sequenc into two groups), so I will borrow that clustering break down that middle group (note this is again, a subjective decision I am making).

pmidVals<- filter(tsneCord, tsneClusterNames == "genom-sequenc") %>% ungroup() %>% select(PMID)
newClust<-clustTopics %>% 
  filter(PMID %in% pmidVals$PMID) %>%
  filter(groupingLevel==75) %>%
  ungroup() %>%
  group_by(cluster) %>%
  mutate(topTerms = getTopTerms(PMID,clustValue=cluster,topNVal = 2))
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
#some small fixes based upon manual inspection (n=1 here)
newClust[which(newClust$topTerms== "infect-studi"), ]$topTerms<-"gene-cell"
newClust[which(newClust$topTerms== "gene-cell"), ]$cluster<-23
#newClust[which(newClust$topTerms== "virus-sequenc"), ]$topTerms<-"viru-sequenc"
idxClust<-match(newClust$PMID,tsneCord$PMID)
idxClust<-idxClust[!is.na(idxClust)]
tsneCord[idxClust,]$tsneCluster<-newClust$cluster
tsneCord[idxClust,]$tsneClusterNames<-newClust$topTerms
#clusterNameLabel
clusterNames <- tsneCord %>%
  group_by(tsneClusterNames) %>%
  summarise(medX = median(comp1),
            medY = median(comp2)) %>%
  filter(tsneClusterNames != "Noise")
tsneCord %>% ungroup() %>% filter(tsneClusterNames != "Noise") %>% count() #11,416
#write using geom_ellipse
tsneCord %>%
  filter(tsneClusterNames != "Noise") %>% 
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames)) +
  geom_point(alpha=0.2,aes(colour=tsneClusterNames))+
  theme_bw()+
  labs(x="tsneComp1",
       y="tsneComp2")+
  stat_ellipse(col="black",alpha=0.5)+
  geom_text(data=clusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",fontface="bold",size=3)+
  theme(legend.position="None")

saveRDS(file="data/rtsne_perplexity_100_withClusters.RDS",tsneCord)

We can also see the size (membership) of those clusters:

tsneCord %>%
  group_by(tsneClusterNames) %>%
  count() %>% 
  ggplot(aes(x=reorder(tsneClusterNames,-n),y=n))+
  geom_bar(stat="identity")+
  theme_bw()+
  xlab("Cluster Name")+
  ylab("Cluster Size")+
  theme(axis.text.x = element_text(angle = 90,hjust=1,vjust=0.5))

It’s notable that nearly 4,000 documents (of ~18000 (22%)) could not be clustered and are classified as Noise. In total, there are 11,416 documents that remain in analysis. There may still be useful data in there, so it may not be a good idea to throw it out. Here is how the noise is scatter among the data:

tsneCord %>%
  mutate(isNoise = ifelse(tsneClusterNames == "Noise","Noise","Cluster")) %>%
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames,colour=isNoise)) +
  geom_point(aes(col = isNoise),alpha=0.2)+
  theme_bw()+
  scale_colour_manual(values=c("#1b9e77","#7570b3"))+
  stat_ellipse()+
  geom_text(data=clusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",size=3)

It’s interesting to note the connection between the hiv-substype and tuberculosis connection. Those documents are registered as noise, however, HIV and TB are common co-infections, and that link between the two of them is an interesting bridge between thses topics. It is noteable that other such bridges are not found between other clusters - this could be because they don’t exist or the signal is not strong enough to survive HDBSCAN’s assumptions.

A reminder that the noise articles that remain, could have been useful at some point in time (based upon the clusterPath analysis), but with a minPtsValue of 150 were classified as noise. There are some islands there that argueably could form useful clusters, but I will rely on the next analysis (bigram clustering) to tease out out that usefulness rather than rely upon the “parameter changing” analysis.

Finally, I will bring back ther “very noisey documents”, those that are pretty much always classified as noise, inspite of the minPts value provided to HDBSCAN.

tsneCordFull<-full_join(tsneCord,veryNoiseyDocuments)
Joining, by = c("PMID", "comp1", "comp2")
tsneCordFull[is.na(tsneCordFull$tsneCluster),]$tsneCluster<- -1
tsneCordFull[is.na(tsneCordFull$tsneClusterNames),]$tsneClusterNames<- "veryNoisey"
tsneCordFull %>%
  mutate(isNoise = ifelse(tsneClusterNames %in% c("Noise","veryNoisey"),
                          ifelse(tsneClusterNames == "Noise","Unclustered","Never Clustered")
                          ,"Clustered")) %>%
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames,col=isNoise)) +
  geom_point(aes(col = isNoise),alpha=0.2)+
  labs(x="tsneComp1",
       y="tsneComp2",
       legend="")+
  theme_bw()+
  stat_ellipse(aes(alpha=isNoise),colour="black")+
  scale_alpha_manual(values=c(0.6,0,0))+
  scale_colour_manual(values=c("#1b9e77","#d95f02","#7570b3"))+
  #scale_colour_manual(values=c("#fb8072","#bebada","#8dd3c7"))+
  geom_text(data=clusterNames,aes(x=medX,y=medY,label=tsneClusterNames),fontface="bold",col="black",size=3)+
  theme(legend.position="below")

The “very noisey documents”" occur everywhere and don’t appear to form distinct clusters as the “noise” documents do. What this says to me is that the “very noisey documents” could very well contain some useful of interesting information, but they are difficult to coax into anyform of structure, again this was pretty much suggested by the HDBSCAN results that consistently failed to put these documents into any one cluster regardless of the parameter valuers. I will not discard the “very noisey documents” outright, but I will continue to exclude them from analysis that establishes clusters and the properties of clusters. The documents that are simply labelled “Noise” appear to form their own clusters and also hangout near known clusters, and might contain some additional useful signal.

3.1.3 Digging into the noise

I don’t want to dig too deeply into the noisey data, but I will look to see if there are any other useful or interesting clusters that form, and how similar or different they may be from the much more pronounced clusters. Even if the smallest cluster size is “2”, there are 538 documents (analysis not show) that don’t really cluster with anything, so not all the documents are very useful. The minimum cluster size that I will use here is 100, this is subjective decision because there are enough articles here to really make a compeling case (to me) that the topic of the cluster is interesting and worth-while to be it’s own entity. Others doing a more nuanced analysis may disagree with this choice.

noiseOnly<- filter(tsneCord,tsneClusterNames == "Noise")
cl <- hdbscan(noiseOnly[,2:3], minPts = 50)
noiseOnly$tsneCluster<-(cl$cluster + 200)
noiseOnly<-noiseOnly %>%
  group_by(tsneCluster) %>%
  mutate(tsneClusterNames = getTopTerms(PMID,clustValue=tsneCluster,topNVal = 3))
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
noiseClusterNames <- noiseOnly%>%
  group_by(tsneClusterNames) %>%
  summarise(medX = median(comp1),
            medY = median(comp2)) %>%
  filter(tsneClusterNames != "Noise") %>%
   filter(tsneClusterNames != "isol-strain-genom")
  
#note - isol-strain-genom is a weird and expansize cluster, that is worth removing.
noiseOnly %>%
  filter(!(tsneClusterNames %in% c("Noise","isol-strain-genom"))) %>%
  ggplot(aes(x=comp1,y=comp2,colour=tsneClusterNames,group=tsneClusterNames)) +
  geom_point(alpha=0.2)+
  theme_bw()+
  stat_ellipse(col="black")+
  geom_text(data=noiseClusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",size=3)+
  scale_y_continuous(limits=c(min(tsneCord$comp2),max(tsneCord$comp2)))+
  scale_x_continuous(limits=c(min(tsneCord$comp1),max(tsneCord$comp1)))+
  theme(legend.position="None")

Finally, I’ll show this with the other known clusters to see how much more useful information clustering the noise provides to the much more established clusters.

idxNoise<-match(noiseOnly$PMID,tsneCord$PMID)
tmp<-tsneCord$tsneClusterNames
tmp[idxNoise]<-noiseOnly$tsneClusterNames
tmp2<-tsneCord
tmp2$tsneClusterNames2<-tmp
#note, useful to view this on a much larger screen
tmp2 %>%
  mutate(isNoise = ifelse(tsneClusterNames == "Noise","Noise","Cluster")) %>%
  filter(!(tsneClusterNames2 %in% c("Noise","isol-strain-genom")))%>% 
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames2)) +
  geom_point(aes(col = isNoise),alpha=0.2)+
  theme_bw()+
  #facet_wrap(~isNoise)+
  stat_ellipse(aes(linetype=isNoise))+
  scale_colour_manual(values=c("#1b9e77","#7570b3"))#+

  #geom_text(data=noiseClusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",size=3)+
  #geom_text(data=clusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="red",size=3)

Subjectively, some of these additional clusters are interesting, and others are not as much. In the subsequent bigram analysis take a look at “pathogens” and “topics”. For my analysis I am mostly interesting in topics, but what is clear is that there is an overwhelming signal from pathogens in these data that make is hard to analyze topics across pathogens. What is also clear is that some of those interesting topics (like outbreaks), form their own clusters instead of being embedded within pathogen clusters. I’ll show in a moment, that outbreak can be found within pathogen clusters too, but I am not certain why “outbreaks” in particular forms its own cluster.

Now I will attempt to see if there are any clusters using the same parameters using the very noisey clusters. Using the smae parameter, only three clusters are formed

cl <- hdbscan(veryNoiseyDocuments[,3:4], minPts = 50)
veryNoiseyDocuments$tsneCluster<-(cl$cluster + 300)
veryNoiseyDocuments<-veryNoiseyDocuments %>%
  group_by(tsneCluster) %>%
  mutate(tsneClusterNames = getTopTerms(PMID,clustValue=tsneCluster,topNVal = 3))
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
Using `n` as weighting variable
Selecting by nn
vNoiseClusterNames <- veryNoiseyDocuments%>%
  group_by(tsneClusterNames) %>%
  summarise(medX = median(comp1),
            medY = median(comp2)) %>%
  filter(tsneClusterNames !="Noise")
  
#note - isol-strain-genom is a weird and expansize cluster, that is worth removing.
veryNoiseyDocuments %>%
  ggplot(aes(x=comp1,y=comp2,col=tsneClusterNames)) +
  geom_point(alpha=0.2)+
  theme_bw()+
  #stat_ellipse(aes(group=tsneClusterNames),col="black")+
  #geom_text(data=vNoiseClusterNames,aes(x=medX,y=medY,label=tsneClusterNames),col="black",size=3)+
  scale_y_continuous(limits=c(min(tsneCord$comp2),max(tsneCord$comp2)))+
  scale_x_continuous(limits=c(min(tsneCord$comp1),max(tsneCord$comp1)))+
  theme(legend.position="None")

Again, these documents don’t have smaller groups that are particularily intersting.

3.2 Bigram clustering

I will now recruit the bigram dataset to help me identify cross-cutting topics between these datasets.At a high level, and using only single terms, the data clustered primarily by disease. But, I know there are cross cutting topics between diseases that should exist, for example, drug resistance, or outbreaks. Those cross-cutting topics are likely burried beneath the disease signal, so I will now try to pull them out using the bigram analysis. I am going to use the clusters from the single term analysis and their co-ordinates to conduct the bigram analysis.

The bigram analysis is particularily useful so that I can figure out what’s going on and how bigrams may be related. This analysis could be done with the single term analysis, but I found that to be messier and more difficult to understand the results, whereas using bigrams was much more useful.

bigram_df<-readRDS(file="data/genEpiData_initialQuery2_BIGRAM_tidyData.RDS")
storedf<-bigram_df
bigram_df<-left_join(tsneCord,bigram_df)
Joining, by = "PMID"
Column `PMID` joining factor and character vector, coercing into character vector
#remove any bigrams that occur in fewer than 10 articles in a cluster
removeBigram <- bigram_df %>%
  ungroup() %>%
  group_by(bigram,tsneClusterNames) %>%
  count() %>%
  filter(nn< 10)
bigram_df <- bigram_df %>% anti_join(removeBigram)
Joining, by = c("tsneClusterNames", "bigram")
#count how often a bigram occurs within each group (each paper only gives 1)
bigramDist<-c()
  
for(clusterName in unique(bigram_df$tsneClusterNames)){
  numPapers<-bigram_df %>%
    ungroup() %>%
    filter(tsneClusterNames == clusterName) %>%
    select(PMID)%>%
    unique() %>%
    count()
    
  tmp<-bigram_df %>%
    ungroup() %>%
    filter(tsneClusterNames == clusterName) %>%
    select(bigram,PMID) %>%
    distinct() %>%
    group_by(bigram)%>%
    count() %>%
    mutate(freq = n/numPapers$n)
  
  tmp$tsneClusterNames<-rep(clusterName,nrow(tmp))
  bigramDist<-rbind(bigramDist,tmp)
}
bigramDistFilt<-bigramDist %>%
  filter(freq > .1) 
#nclust
nClust<-bigram_df$tsneClusterNames %>% unique() %>% length()
#finally, tabulate which bigram occur the most frequently across clusters
tmp<-bigramDistFilt %>%
  select(bigram,tsneClusterNames) %>%
  distinct() %>%
  group_by(bigram) %>%
  count() %>%
  mutate(freq = n / nClust)
write.csv(tmp,file="data/bigramCrossCluster_2.csv")

I have manually annotated those terms to cluster them into higher level categories. I will now explore the bigram terms, how I’ve clustered manually annotated them, and what I was expecting to see. Currently, each bigram has only ONE annotation.

manAnnot<-read.csv(file="data/bigramCrossCluster_2_manualAnnot.csv",header=T)
manAnnot$Annotate<-trimws(manAnnot$Annotate)
#first, just show how many bigrams I've assigned to some annotation
manAnnot %>%
  group_by(Annotate) %>%
  count() %>%
  ggplot(aes(y=nn,x=reorder(Annotate,-nn))) +
  geom_bar(stat="identity")+
  theme_bw()+
  geom_text(aes(label=nn),nudge_y=3,col="red")+
  xlab("Manually annotate categories")+
  ylab("Total number of bigrams")+
  theme(axis.text.x=element_text(angle=90,vjust=0.5,hjust=1))

The specific decisions behind each of the manually annotations are also noted in the CSV file for the manual annotations. I need to now make some decisions about what I keep, what I nest under some level (if at all), and whether I should go back and consider assignment bigram to multiple tags.

To begin, the most common tag is the “not-useful” tag. I’ve often classified a term as “not-useful” (for the purposes of my analysis) if it is too general, (“genom_sequenc”), a method (“chain_reaction”, “confiden_intrv”,“electrophoresi_pfge”), or if it was just english writing (“analysi_reveal”). I do not include the “not-useful” bigrams in further analysis.

The next most common category is “pathogen”, these are commonly occuring bigrams that basically describe a bug. For example “dengu_vir”, which matches the cluster names from the single term analysis. I consider the bigrams pathogen_x, or x_pathogen, to belong to the pathogen annotation if x = “vir”,“isol”,“virus” or if x is the species name (i.e “staph aureus”). If x = “strain”, the annotation is currently “characterisation”, however this might also be a good case for allowing a bigram to belong to multiple annotations. Some pathogens found in the bigram analysis where not picked up by the single term clusterings, for example, there is no “klebsiella_pneumonia” cluster, but there is an “influenza” cluster. In a moment I’ll show how many “new” pathogens are found by the bigram analysis, and to which clusters they belong. But lastly, and most importantly, the “pathogen”" annotation isn’t really a cross-cutting topic, it just happens to show up. I’d like to make sure that I study the visualization across pathogens, which include capturing some of the pathogens that don’t form their own clusters.

pathAnnot<-manAnnot %>%
  filter(Annotate == "pathogen") %>%
  select(bigram,Annotate)
pathCluster<-bigram_df %>%
  inner_join(pathAnnot)
Joining, by = "bigram"
Column `bigram` joining character vector and factor, coercing into character vector
sortOrder<-pathCluster %>%
  ungroup() %>%
  select(bigram,tsneClusterNames) %>%
  distinct() %>%
  group_by(bigram)%>%
  count() %>%
  arrange(-n)
pathClusterCount<- pathCluster %>%
  ungroup()%>%
  select(bigram,tsneClusterNames) %>%
  group_by(bigram,tsneClusterNames)%>%
  count()
pathClusterCount$bigram <- factor(pathClusterCount$bigram,levels=sortOrder$bigram)
ggplot(pathClusterCount, aes(x=tsneClusterNames,y=bigram)) +
  geom_tile(aes(fill=n),colour="white")+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

It’s clear that a lot of interesting stuff is in the “Noise” category, where some pathogen really just didn’t get clustered. It’s possible to show where everything ends up, either just on the outside of existing clusters or within them. It is also evidence that pathogens cross-cut a number of groups in the middle that have nothing really to do with pathogens, but are more about topics. For example, the E.coli pathogen bigram is predominately fround in the “coli-strain” cluster, but occrus across a number of pther clsuters (mlva-isol, resist-isol) ect.

bigramNames <- pathCluster %>%
  group_by(bigram) %>%
  summarise(medX = median(comp1),
            medY = median(comp2))
tsneCord %>%
  ungroup()%>%
  mutate(isPathBigram = ifelse(PMID %in% unique(pathCluster$PMID),"pathogen","not-pathogen")) %>%
  mutate(isNoise = ifelse(tsneClusterNames == "Noise","Noise","Cluster")) %>%
  ggplot(aes(x=comp1,y=comp2,group=tsneClusterNames)) +
  geom_point(aes(col = isPathBigram,alpha=isPathBigram))+
  scale_alpha_manual(values=c(0.1,0.7))+
  scale_colour_manual(values=c("grey","blue"))+
  stat_ellipse(aes(linetype=isNoise),col="darkgrey")+
  #geom_text_repel(data=bigramNames,aes(x=medX,y=medY,label=bigram),col="black",size=3)+
  theme_bw()

So what is clear is that clusters around the outside (away from the middle), are all disease topics, whereas the stuff in the middle doesn’t really have anything to do with pathogens, I am not yet sure what’s in those clusters in the center, but I’ll figure it out as I move through the annotations. It seems that many articles classified as noise might just be on the out reaches of known clusters. It seems that some clusters could even be broken up some more. For example isol_resist seems to actually contain klebsilla and psuedemonas data. E. coli seem to be all over the place, but largely relegated to noise and same with heliobacter_pylori , those may require a little bit more tweaking.

One other neat thing, it seems that the data also seperate into bacteria (toward the right of the figure) and viruses (toward the left of the figure).

On a final note, there are certainly some bugs here that aren’t represented and I’d like to know a little bit more of why. Where is measles for example, or where is ebola? Where are any of the sexually transmitted diseases. What’s possible is that the articles that dominate genomic epidemiology for infectious diseases are predominately about the major pathogens that are easily visible in the clustering analysis. But measles papers are more of a “public health” and less of “genomics” area. Since I want to retrieve genomics articles, this could be fine, but I want to see if that is actually the case. To do so, I’ll explore where certain pathogens I expect to find exist, in this data set, and if my hypothesis is correct, I would tend to find these bugs in that “centre” group, that is current devoid of pathogen bigrams.

So what I think is happening here is that the methods or even cross-cutting topics signal is greater than the pathogen signal. That is, “pcr”,“virus sequence” etc have much stronger signal (because there are more documents) compared to “ebola virus” which has fewer documents, so everything kinda gets lumped into a free for all in the middle. It makes some sense, because viruses like Ebola or Zika only because priorities for investigation as these outbreaks because to take off in 2014 - 2017. By comparison, genomic analysis of TB, HIV, and influenza have a much longer history.

It could be possible to find a tag using “_viru“,”_isol“, or”_strain" (which came up a lot as patterns in the more prominent pathogen group) to see what I pull up from the “none-pathogen” clusters.

This is still a bit too general, so I will bring in a list of known communicable diseases that effect humans, to find where they are in the data set, and if they are present, what bigram prefixes or suffixes are common.

input string 104 is invalid in this localeJoining, by = "bigram"
Joining, by = "bigram"
Column `bigram` joining character vector and factor, coercing into character vectorJoining, by = c("PMID", "comp1", "comp2", "tsneCluster", "tsneClusterNames", "bigram")
[1] 7191

So, browsing these labels, what I can tell is that there are other pathogens, they’re just not very common human pathogens or they’re not human pathogens at all, “canine picovirus” for example, or they are review articles about certain families of viruses. So, they’re not random articles, there are just very few articles about those topics such that they don’t form a specific cluster, and they’re not very very relevant human pathogens because they are absent from my list like Ebola and Zika which still don’t have as many articles relative to TB, but that are of more contemporary interest.

The other label that doesn’t quite fit into the area of cross-cutting topics is “disease”, which could refelect a specific pathogen or could refelect a condition that does cross-cut pathogen groups (for example “respiratory infections”, are caused by a much of different pathogens). I marked pathogens and disease differently, but now I’ll make an assessment if that was the right choice.

diseaseAnnot<-manAnnot %>%
  filter(Annotate == "disease") %>%
  select(bigram,Annotate)
diseaseCluster<-bigram_df %>%
  inner_join(diseaseAnnot)
Joining, by = "bigram"
Column `bigram` joining character vector and factor, coercing into character vector
diseaseCluster %>%
  ungroup()%>%
  select(bigram,tsneClusterNames) %>%
  group_by(bigram,tsneClusterNames)%>%
  count() %>%
  ggplot(aes(x=tsneClusterNames,y=bigram)) +
  geom_tile(aes(fill=n),colour="white")+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

Diseases are rather similar to pathogens, I might not want to keep this annotation because “pathogens” seems to be sufficent.

The rest of the annotations are all cross-cutting topics, and it would be useful to see how they are distributed across the different groups, and particularily how they’re distributed within that middle group.

allAnnot<-manAnnot %>%
  filter(!(Annotate %in% c("disease","pathogen"))) %>%
  select(bigram,Annotate)
allAnnotCluster<-bigram_df %>%
  inner_join(allAnnot)
Joining, by = "bigram"
Column `bigram` joining character vector and factor, coercing into character vector
sortOrder<-allAnnotCluster %>%
  ungroup() %>%
  select(Annotate,tsneClusterNames) %>%
  distinct() %>%
  group_by(Annotate)%>%
  count() %>%
  arrange(-n)
allAnnotCluster %>%
  ungroup()%>%
  select(Annotate,tsneClusterNames) %>%
  group_by(Annotate,tsneClusterNames)%>%
  count() %>%
  ungroup() %>%
  mutate(Annotate= factor(Annotate,levels = as.character(sortOrder$Annotate))) %>%
  ggplot(aes(x=tsneClusterNames,y=Annotate)) +
  geom_tile(aes(fill=n),colour="white")+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

Looking at the cross-cutting topics, a few things stand out. The “not-useful”, annotation is found most frequently in the “Noise” cluster, but occurs across all the others as well. Drug resistance bigram occur primarily in the pathogen clusters, as well as “strain-isol” and “mrsa-isol” clusters. Vaccine bigrams are not present in the influenza clusters, which is a bit surprising, but the noisey document analysis suggest that this is because they may for their own clusters outside of the more general inflenza group.

Now that I have a sense from this analysis that pathogens should drive the analysis, it would be good to find cross-cutting topics more directly linked to pathogen terms. For exaple “ebola outbreak”, “influenza_outbreak”,“tuberculosis outbreak”, making “outbreak” a useful cross-cutting topic. This might be much more effective then just looking for terms the signify resistance, for example “hospital outbreak”, “community outbreak”, which are concepts that could occur within pathogens clusters. So, I’ll try that now and see what I get

3.2.1 Bigrams by pathogens

I won’t use all of the pathogens - I am going to use the documents from the tsne clusters with pathogen annotations (already evident in the analysis) and also add some additional items that are not in pathogen clusters, but are interesting from the previous pathogen analysis (like zika, ebola, etc.).

Adding missing grouping variables: `tsneCluster`

Adding missing grouping variables: `tsneCluster`

From this I have 6,350 articles that represent specific pathogens that both a very widely studied (for tsne clusters) or are less studied but still interesting (low signal, high interest; i.e ebola virus). It’s clear from the above figure that disease in the middle clusters are quite spread out (at least according to the original clustering). We might get a cleaner image by re-clustering using tsne, but that’s not very important for my next step.

So, now let’s try to big cross-cutting topics between these bugs. Some of these might be similar to previous manual annotations created by considering the whole data set, but also annotaiton of topics linked to specific terms (i.e. “ebola outbreak”, instead of “hospital outbreak”).


bigram_path_df<-inner_join(bigram_df,favBugsArticles[,c("PMID","Pathogen")]) 

#other topics
crossTopic<-bigram_path_df%>%
  filter(Pathogen !="Ignore") %>%
  group_by(bigram,Pathogen)%>%
  count() %>% 
  filter(nn > 2) %>%
  ungroup() %>%
  group_by(bigram) %>%
  count() %>%
  mutate(topic = bigram) %>%
  separate(topic,c("word1","word2"),"_")


#next, remove terms that only occur in one cluster. Now that I know what the pathogens are, I don't need the pathogen 

write.csv(file="data/topicsByPathogen.csv",crossTopic,quote=F)

When I look at the cross-cutting topics, I can see the common themes, much more clearly when considering pathogen documents instead of just the tsne clusters as I had considered previously.

I’ll now load the manual annotations to see how bigram and annotations play out across the different clusters, and finally, how many documents contain some useful annotations (some bigrams are tagged as ignore). The annotations were subjectively selected, others may find items I’ve tagged as “ignore” to be very interesting. But generally, items that were labelled as “ignore” had to do with common phrasing (“analysis revealed”), generic molecular biology mechanisms (like reporductive mechanisms; note, mechanisms specific to drug resistance (plasmids, resistance genes) were retained and tagged as mBio), generic immune responses (hla type, innate immunity; these don’t have much to do with epidemiology even though they are relevant to the disease); regions (Africa, China, USA ect.); and re-iterations of already identified pathogens. Finally some terms were just very broad, and other more specific terms were sometimes better to use. note to self: use the bigram dataset that hasn’t been filtered for different levels of noise because there are useful articles floating around

manAnnot<-read.csv(file="data/topicsByPathogen_manuallAnnot.csv",header=T)


#Now, since I have the bigrams for all the useful bugs filtered out from the noisey documents, I will include every single document (with all levels of noise) that map to a document 

#now get total number of bugs
totalBugs<-pathogensFinal %>% ungroup() %>% filter(Pathogen != "Ignore") %>% select(Pathogen) %>% unique() %>% count()
totalBugs<-totalBugs$n

#first, the most common bigrams and how many pathogens they are present in
manAnnot %>%
  filter(n>1)%>%
  group_by(n) %>%
  count() %>% 
  ggplot(aes(x=n,y=nn))+
  geom_bar(stat="identity")+
  geom_text(aes(label=nn),nudge_y=5,col="red")+
  xlab("Total # of pathogens bigram occurs in")+
  ggtitle("Bigrams across pathogens")+
  scale_x_continuous(breaks=2:totalBugs)+
  theme_bw()

The majority of terms occur in just one pathogen (~1,300 bigrams), while some occur in all pathogens ( “genome sequence”). Bigrams are also specific terms (espeically those that just occur in one pathogen) that can describe the same concept. For example “Beta lactamase” is associated with drug resistance, but might be referenced in different ways (abbreviation for example). So I manually annotated each of these bigrams to concepts that they describe.

manAnnot %>%
  group_by(why) %>%
  count() %>% 
  filter(why !="ignore") %>%
  ggplot(aes(x=reorder(why,-nn),y=nn))+
  geom_bar(stat="identity")+
  theme_bw()+
  geom_text(aes(label=nn),nudge_y=5,col="red")+
  ggtitle("Annotations for Bigrams")+
  xlab("Annotation terms")+
  ylab("Total number of bigrams assigned to annotation term")+
  theme(axis.text.x = element_text(angle=90,vjust=0.5,hjust=1))

To some bigrams I have given more than one annotation since it could represent multiple concepts. In my mind there is a hiearchy here, but I’ll break them up.

manAnnot %>%
  mutate(why = strsplit(as.character(why),split=";")) %>%
  unnest(why)%>% 
  group_by(why) %>%
  count() %>% 
  filter(why !="ignore") %>%
  ggplot(aes(x=reorder(why,-nn),y=nn))+
  geom_bar(stat="identity")+
  theme_bw()+
  geom_text(aes(label=nn),nudge_y=5,col="red")+
  ggtitle("Bigrams per concept")+
  xlab("Concept terms")+
  ylab("Total number of bigrams assigned to annotation term")+
  theme(axis.text.x = element_text(angle=90,vjust=0.5,hjust=1))

So most annotations (concepts) have a number of bigrams associated with that concept, with only a few (community, international, outcome, and reservoir) have only one associated bigram. The annotations here were much easier to come up than my initial attempt that used just the clusters and not the pathogen (again, even though there is an overlap between clusters and pathogens).

Finally, I will now show how these concepts occuring accross the different pathogens.

bigram_path_df%>%
  filter(Pathogen !="Ignore") %>%
  inner_join(manAnnot[,c("bigram","why")]) %>% 
  filter(why !="ignore") %>% 
  ungroup() %>% 
  mutate(why = strsplit(as.character(why),split=";")) %>%
  unnest(why)%>% 
  group_by(why,Pathogen)%>%
  count() %>%
  ungroup() %>%
  group_by(why) %>%
  count() %>% 
  ggplot(aes(x=reorder(why,-n),y=n))+
  geom_bar(stat="identity")+
  theme_bw()+
  geom_text(aes(label=n),nudge_y=1,col="red")+
  xlab("Concept")+
  ylab("# of Pathogens with this concept")+
  ggtitle("Concepts by Pathgens")+
  theme(axis.text.x = element_text(angle=90,vjust=0.5,hjust=1))

So these concepts have much more coverage than individual bigram terms. Lasty, it is possible to see how many articles, per concept and pathogen exist. Up to this point in the analysis, I’ve removed very noisey articles that really didn’t cluster with others. I will now bring those articles back in (except the very noisey documents), and if they contain bigrams that relate to what I’ve found, then they’re fair game.


dat<-bigram_path_df%>%
  filter(Pathogen !="Ignore") %>%
  inner_join(manAnnot[,c("bigram","why")]) %>% 
  filter(why !="ignore") %>% 
  ungroup() %>%
  mutate(why = strsplit(as.character(why),split=";")) %>%
  unnest(why)

saveRDS(file="data/concepts_bigrams_articles_Final.RDS",dat)

dat %>%
  select(PMID,why,Pathogen) %>%
  group_by(why,Pathogen) %>%
  distinct() %>% 
  count()%>%
  filter(n>1) %>% #remove concepts that occurs only in one article (very little signal or support)
  ggplot(aes(x = why,y=Pathogen))+ #not perfect, but good enough
  geom_tile(aes(fill=n),colour="white")+
  theme_bw()+
  xlab("Concept")+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

From the above figure, sthe presence and absence of some concepts make sense. For example, the “cancer” concept is found in “H. plyori, HCV,HBV, and Hepatitis-General,and HPV” which make sense as these pathogens are more directly invovled in tumour aetiology. The terms, “Phylogeny outbreak,genome,characterization” and drug resistance occur in a lot of pathogens. There are some interesting concepts represented in some pathogens. For example “transmission” in microbiota, which seems odd, but looking digging into it a bit seems to be legitmate because the papers concerns how microbiota concern susceptibility for transmission for other pathogens. Which is interesting, but is different from say the chain of transmission with other pathogens. So there’s still a fair bit of nuance. There’s also some articles that will be very methods heavy (like those flagged as characterization and mBio), which really doesn’t have much to do with genomic epidemiology, but more about laboratory techniques. Those don’t have much to do with visualization, but may contain some interesting of useful visualizations.

Finally, I want to know, how many “concepts”" there are per article. Would some articles only have one concept, would other articles have more than one.


dat %>%
  select(PMID, why) %>%
  distinct() %>% #some articles may contain bigrams that map to the same concept multiple times
  group_by(PMID) %>%
  count() %>% 
  ggplot(aes(x=n))+
  geom_histogram(binwidth =1,colour="white")+
  theme_bw()

The majority of articles are linked to only one to three interesting concepts. The articles that have more can be interesting, but don’t always have full text links. We can also split this up by pathogen to see which pathogen has papers with a single concept.

dat %>%
  select(PMID, why,Pathogen) %>%
  distinct() %>% #some articles may contain bigrams that map to the same concept multiple times
  group_by(PMID,Pathogen) %>%
  count() %>%
  ggplot(aes(x=n))+
  geom_histogram(binwidth =1,colour="white")+
  facet_wrap(~Pathogen,scales="free_y")+
  theme_bw()

Not very surprisingly, the pathogens that would pretty much form their own clusters on TSNE analysis also have a lot of concepts represented among them, and this is really because there’s been enough published about them over very many years. So what I will do is keep that pathogens with a lot of concepts as their own clusters, and everything else, I will place into an “other” category.


total_docs <- dat %>% 
  select(PMID, Pathogen) %>%
  distinct() %>%
  group_by(Pathogen)%>%
  summarize(total = n()) %>% 
  filter(total>100) %>% 
  select(Pathogen)

# keepGroup<-dat %>%
#   select(PMID, why,Pathogen) %>%
#   distinct() %>% #some articles may contain bigrams that map to the same concept multiple times
#   group_by(PMID,Pathogen) %>%
#   summarise(nConcepts = n()) %>%
#   ungroup()%>%
#   group_by(Pathogen,nConcepts)%>%
#   summarise(totalDocs = n()) %>% 
#   inner_join(total_docs) %>% 
#   ungroup() %>%
#   mutate(freq = totalDocs/total) %>% 
#   filter(nConcepts == 1) %>% 
#   filter(freq<0.25) %>% #fewer than 25% of articles are linked to only one concept
#   select(Pathogen)

dat %>%
  select(PMID, why,Pathogen) %>%
  distinct() %>% #some articles may contain bigrams that map to the same concept multiple times
  mutate(Pathogen = factor(ifelse(Pathogen %in% total_docs$Pathogen, Pathogen, "Other"),levels=c(total_docs$Pathogen,"Other"))) %>%
  group_by(PMID,Pathogen) %>%
  count() %>%
  ggplot(aes(x=n))+
  geom_histogram(binwidth =1,colour="white")+
  facet_wrap(~Pathogen)+
  theme_bw()

When I sample articles, I will try to sample concepts from across 18 groups, this will result in about 414 publications. If a paper that is sampled ends up not having a relevant visualization, then I will sample again (specifically for that pathogen and concept) until I get one that does.

4 Sampling documents

Based upon the above analysis, I’ve concluded that it’s easiest to treat the concepts as tags rather than as having some more innate hiearchical structure.

My strategy to sample documents is to make sure every concept is covered per pathogen. In theory, if each paper had only one affiliated tag, I would need to sample 23 unique papers for 18 pathogens, resulting in a final dataset of 414 papers that for the following analysis of visualizations. Since papers tend to have more than one concept affiliated with them, the total number of papers will actually be quite a bit fewer. I personally am aiming for a few hundred documents so that I have a good diversity for analysis, and so, I might do redundant coverage of concepts with a pathogen depending upon how things shake out. Also, some pathogens just won’t have a specific concept associated with it at all (like reservoir, which only occurs in 3 pathogens; this doens’t mean only three pathogens have reservoirs, but that only three pathogens have those bigram terms associated with at and there is some signal in the data to pick it up)

Following the initial sampling I will manually review each of the suggested documents to see if they contain a visualization that is relevant (for example some papers may just have pictures of gels). If there are no visualizations, or the visualizations are not relevant, I will resample documents (for a given pathogen) until I once again have coverage of all documents and relevant data visualizations. Also, if it seems like a document has a visualization I’ve already seen (I expect many phylogenetic trees without annotations (note, phylogenetic trees with differents kinds of annotations are interesting and count as different things)) I will also resample to try get some diversity. I will keep a record for the decisions that were made along the way.

4.1 Sampling round 1

set.seed(416)

docStore<-c()

#sort from pathogen with least papers to most, start with least papers.
concepts<-dat %>%
  dplyr::select(PMID,Pathogen,why)%>%
  distinct()%>%
  mutate(Pathogen = factor(ifelse(Pathogen %in% total_docs$Pathogen,Pathogen, "Other"),levels=c(total_docs$Pathogen,"Other"))) %>%
  group_by(why) %>%
  count() %>%
  arrange(n)


for(concept in concepts$why){
  #select documents
  docSample<-dat %>%
    dplyr::select(PMID,why,Pathogen) %>%
    mutate(Pathogen = factor(ifelse(Pathogen %in% total_docs$Pathogen,Pathogen, "Other"),levels=c(total_docs$Pathogen,"Other"))) %>%
    distinct()%>%
    filter(why == concept) %>% 
    group_by(Pathogen)%>%
    sample_n(1)
  
 
  docStore<-rbind(docStore,docSample)
}

genEpiData <- readRDS(file="data/genEpiData_initialQuery2.RDS")

#276 documents
genEpiData<-genEpiData %>%
  filter(PMID %in% replacement$PMID)

#for each PMID, I will summarize the concept tags that are within each documents
genEpiData$concepts<-rep(NA,nrow(genEpiData))
genEpiData$Pathogen<-rep(NA,nrow(genEpiData))
genEpiData$replacementPMID<-rep(NA,nrow(genEpiData))

conceptCoverage<-c()
pathogenCoverage<-c()

for(i in 1:nrow(genEpiData)){
  PMIDval<-genEpiData[i,"PMID"]
  
  conceptTags<-filter(datStore,PMID == PMIDval) %>%
    dplyr::select(why,Pathogen) %>%
    distinct()
  
  conceptCoverage<-c(conceptCoverage,unique(conceptTags$why))
  pathogenCoverage<-c(pathogenCoverage,unique(conceptTags$Pathogen))
  
  genEpiData[i,"concepts"]<-paste0(unique(conceptTags$why),collapse=";")
  genEpiData[i,"Pathogen"]<-paste0(unique(conceptTags$Pathogen),collapse=";")
}

View(dplyr::select(genEpiData,-contains("meshTerms")))
 
#write.csv(file="data/documentSample_v0.1.1csv",dplyr::select(genEpiData,-contains("meshTerms")),quote=T)

#finally, the concept coverage by these 276 articles

sort(table(conceptCoverage))

sort(table(pathogenCoverage))

Note to future self: Version 1.0 of the sampling was me trying things out, if someone ran through this analysis, the might not get the documents in version 1.0.

Verion 1.1 (the version used in analysis) was obtained when I ran through the entire analysis as is, not extra analysis or figuring things out. In theory, someone running through this analysis should end up with the same sampled documents (since I set a seed) as articles.

Review of Sampled Documents

I have now analyzed the 276 documents and classified them as Y (acceptable for further analysis), N (not acceptable for further analysis), or M (I’ve included it for now, but I was on the fence about it).


sampDoc<-read.csv(file="../data/documentSample_v0.1.1_CSV.csv",header=T,stringsAsFactors = F)

#clean up any white space
sampDoc$Include<-trimws(samp)

table(sampDoc$Include)

So far, I have included only 107 documents, a success rate of ~40%, or only 32.6% for articles that were confidently included. The other articles have been excluded for various reasons that are indicated in the excel document.

It’s also possible to see inclusion by year:

table(sampDoc$YearPub,sampDoc$Include..Y.N.)

Articles in 2010 and beyond were more likely to be included. For resampling efforts it might be more fruitful to sample later years, rather than sample publications from much earlier.

Finally, we can also see the concepts covered by the sampled documents

#concept path covers the topics covered in the accepted document
conceptpath<-sampDoc %>%
  filter(Include == "Y") %>%
  mutate(concepts = ifelse(revised.concepts=="",concepts,revised.concepts))%>% 
  mutate(concepts = strsplit(as.character(concepts), ";")) %>%
  tidyr::unnest() %>%
  #fix a typo
  mutate(concepts = ifelse(concepts == "surveillence","surveillance",trimws(concepts))) %>%
  mutate(Pathogen = strsplit(as.character(Pathogen), ";")) %>%
  tidyr::unnest() %>%
  ungroup()%>%
  group_by(Pathogen,concepts)%>%
  count() %>%
  ungroup()%>%
  tidyr::complete(Pathogen, concepts, fill = list(n = 0))

tmpOrderConcept<- conceptpath %>% ungroup() %>% group_by(concepts) %>% summarise(nn=sum(n)) %>% arrange(-nn)
tmpOrderPathogen<- conceptpath %>% ungroup() %>% group_by(Pathogen) %>% summarise(nn=sum(n)) %>% arrange(-nn)

conceptpath$concepts<-factor(conceptpath$concepts,levels=tmpOrderConcept$concepts)
conceptpath$Pathogen<-factor(conceptpath$Pathogen,levels=tmpOrderPathogen$Pathogen)

ggplot(data=conceptpath,aes(y=concepts,x=Pathogen))+
  geom_tile(aes(fill=n),colour="white")+
  scale_fill_gradient(low="#ffffff",high="#000000")+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

4.2 Sampling round 2

The goal of resampling is to replace pathogens and concepts that we classified as N or M in the first round of reviews. Items were classified as N if they contained no data visualizations, or were off topic, or didn’t really talk about humans (with some very few exceptions). The various reasons are listed in the excel document. Note that just lab images (pfge gels, microscopy images) were not considered to be data visualizations. Some papers had pfge gels + a phylogenetic tree for annotation, and that was considered to be a data visualization.

The sampling here will go as follows: two attempts will be made to resample each pathogen and concept combination, since most papers cover multiple topics priorty is given to a topic that isn’t yet convered by that pathogen and afterwards, a topic that is not well covered in general. The first of the resampled papers that gets a Y is accepted (if the first of two is a good hit, then the second sample is discarded).

I noticed while going through the first batch of 276 papers that many older papers were harder to access or contained fewer data visualizations. So this next batch of samples will be limited papers published in 2011 and beyond. This should hopeful sample some papers that aren’t just phylogenetic trees with annotating text.

set.seed(430)

conceptsInitial<-conceptpath

#remove documents already sampled
idx2011<-filter(genEpiData,YearPub>=2011) %>% dplyr::select(PMID)
datSub <- filter(dat, !(PMID %in% sampDoc$PMID)) %>% filter(PMID %in% idx2011$PMID)

#replace a previous paper that was rejected (N)
rejectedPMID<-filter(sampDoc, Include == "N") %>% dplyr::select(PMID)

replacement = c()

for(rejectedPaper in rejectedPMID$PMID){
  tmp<-dat %>% filter(PMID == rejectedPaper) %>%
    dplyr::select(Pathogen,why) %>% 
    distinct()
    
    #when a single article covers many many pathogens,
    #sample the pathogen with the least number of topics covered.
    #this doesn't happen a lot, but some articles are about a lot of bugs
    nPath<-length(unique(tmp$Pathogen))
    
    if(nPath > 1){
      pathSamp <-  tmpOrderPathogen$Pathogen[max(which(tmpOrderPathogen$Pathogen %in% unique(tmp$Pathogen)))]
    }else{
      pathSamp <- unique(tmp$Pathogen)
    }
    
    #for that pathogen, sort concepts according to coverage
    topicOrder<-filter(conceptpath,Pathogen == pathSamp) %>%
      arrange(n) %>%
      ungroup() %>%
      group_by(n)%>%
      sample_frac(1)#this randomly orders items within the same count level (see rationale below)
    
    # some bugs that were sampled had *no* previous documents sampled (conceptpath
    # contains sampled documents that were *not* rejected.). This can be the case for
    # bugs like Zika and Ebola which are newer, and got in that "other" category. So
    # pick any concept to sample
    if(nrow(topicOrder)==0){
      topicOrder<-data.frame(concepts =levels(conceptpath$concepts),
                             n = rep(0,length(levels(conceptpath$concepts))))
    }
    
    holding<- c() #keeping track of what's been sampled, again, only need two documents
    
    # sample the least sampled topics first, and work up the list
    # randomly order topics withing the same count (see above)
    # sometimes "holding" has a PMID stored which has not yet been pushed
    # to replacement, this can cause some duplicated PMIDs, and I've decided
    # that's fine, since the number of replacated IDs is few and there are
    # "rare" concepts/pathogen combinations which may only end up with one article anyway
    for(topic in topicOrder$concepts){
      tmp2<- filter(datSub, Pathogen == pathSamp & why == topic)
      
      #try to sample as many unique articles as possible
      if(!(is.null(replacement)) & nrow(tmp2)>1){
        tmp2<-filter(tmp2,!(PMID %in% replacement$PMID))
      }

      if(nrow(tmp2)==0){
        #move on to the next topic, there's nothing to see here
        next;
      }else if(nrow(tmp2)==1){
        #if there's only one paper to pick from, select it then move on to the next topic
        tmp2<-cbind(tmp2,rejectedPaper)
        names(tmp2)<-c(names(tmp2)[1:(ncol(tmp2)-1)],"rejectedPMID")
        holding<-rbind(holding,tmp2)
      }else if(nrow(tmp2)>1){
        if(is.null(nrow(holding))){
           #if there are more than two papers to pick from, randomly sample 2
            tmp2<-cbind(dplyr::sample_n(tmp2,2),rep(rejectedPaper,2))
        }else if(nrow(holding)==1){
          tmp2<-cbind(dplyr::sample_n(tmp2,1),rejectedPaper)
        }
       
        names(tmp2)<-c(names(tmp2)[1:(ncol(tmp2)-1)],"rejectedPMID")
        holding<-rbind(holding,tmp2)
      }
      
      if(nrow(holding) == 2){
        #update the replacement object
        replacement<-rbind(replacement,holding)
        
        #update the concept count option - the lazy way
        #note to self - I checked and this works woo!
        #so next time a pathogen needs to replace paper we get undersampled topics!
        for(i in 1:2){
          pathSamp<-holding[i,"Pathogen"]
          topic<-holding[i,"why"]
          
          conceptpath<-conceptpath %>%
            ungroup()%>%
            mutate(n=ifelse(Pathogen == pathSamp & concepts == topic, n + 1,n))
        }
        
        holding<-c()
        break;
      }else{
        next;
      }
    }
}

p1<-ggplot(data=conceptsInitial,aes(y=concepts,x=Pathogen))+
  geom_tile(aes(fill=n),colour="white")+
  scale_fill_gradient(low="#ffffff",high="#000000")+
  theme_bw()+
  theme(legend.position="none",axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

p2<-ggplot(data=conceptpath,aes(y=concepts,x=Pathogen))+
  geom_tile(aes(fill=n),colour="white")+
  scale_fill_gradient(low="#ffffff",high="#000000")+
  theme_bw()+
  theme(legend.position="none",axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

cowplot::plot_grid(p1,p2)

Below, highlight the areas where the sampling scheme tried to boost signal

diffConcept<-conceptsInitial %>%
  ungroup() %>%
  mutate(newCounts = conceptpath$n) %>%
  mutate(diff = newCounts - n)

ggplot(data=diffConcept,aes(y=concepts,x=Pathogen))+
  geom_tile(aes(fill=diff),colour="white")+
  scale_fill_gradient(low="#ffffff",high="#000000")+
  theme_bw()+
  theme(axis.text.x = element_text(angle=90,hjust=1,vjust=0.5))

Ok, the total number of resampled documents matches what I expect (310).

replacement %>% 
  ungroup()%>%
  group_by(rejectedPMID) %>%
  count() %>% 
  filter(nn < 2) #no that's not it

setdiff(rejectedPMID$PMID,replacement$rejectedPMID) #ok, six had something weird
genEpiData<-genEpiData %>%
  filter(PMID %in% replacement$PMID)

#for each PMID, I will summarize the concept tags that are within each document

genEpiData$concepts<-rep(NA,nrow(genEpiData))
genEpiData$Pathogen<-rep(NA,nrow(genEpiData))
genEpiData$replacingPMID<-rep(NA,nrow(genEpiData))

for(i in 1:nrow(genEpiData)){
  PMIDval<-genEpiData[i,"PMID"]
  
  conceptTags<-filter(datSub,PMID == PMIDval) %>%
    dplyr::select(why,Pathogen) %>%
    distinct()
  
    replacementPMID<-filter(replacement,PMID == PMIDval)
  
  genEpiData[i,"concepts"]<-paste0(unique(conceptTags$why),collapse=";")
  genEpiData[i,"Pathogen"]<-paste0(unique(conceptTags$Pathogen),collapse=";")
  genEpiData[i,"replacingPMID"]<-paste0(unique(replacementPMID$rejectedPMID),collapse=";")
}

View(dplyr::select(genEpiData,-contains("meshTerms")))
 
#write.csv(file="data/document_Resample_v0.1.0.csv",dplyr::select(genEpiData,-contains("meshTerms")),quote=T)
LS0tCnRpdGxlOiAiR0VWaVQgVGV4dCBtaW5pbmcgYW5hbHlzaXMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgZmlnX2hlaWdodDogNgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCi0tLQoKIyBPYnRhaW5pbmcgUHVibWVkIGRhdGEgc2V0CgpUaGVyZSBhcmUgc2V2ZXJhbCBjb2RlIGJsb2NrcyB0aGF0IGFyZSBub3QgcnVuIGluIHRoaXMgbm90ZWJvb2sgYmVjYXVzZSBpdCB3b3VsZCB0YWtlIHRvbyBsb25nIG9yIHRha2UgdG9vIG11Y2ggUkFNLiBDb25zZXF1ZW50bHkgYXJlIGluc3RhbmNlcyB3aGVyZSB0aGUgY29tcHV0YXRpb24gaGFzIGJlZW4gZG9uZSBvbiBhbiBleHRlcm5hbCBzZXZlci4gQWxsIHRoZSBkYXRhc2V0cyBnZW5lcmF0ZWQgYWxvbmcgdGhlIHdheSBhcmUgc3RvcmVkIGluIHRoZSAiZGF0YSIgc3ViZm9sZGVyLgoKYGBge3Igc2V0dXAsbWVzc2FnZT1GQUxTRSwgd2FybmluZz0gRkFMU0UsIGVjaG89RkFMU0V9CmxpYnJhcnkoUklTbWVkKQpsaWJyYXJ5KFhNTCkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeSh0aWR5cikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHRzbmUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkYnNjYW4pCmxpYnJhcnkoU25vd2JhbGxDKQpsaWJyYXJ5KGdncmVwZWwpCgojc29tZSBzdXBwb3J0aW5nIHNjcmlwdHMgSSd2ZSB3cml0dGVuCnNvdXJjZSgidXRpbGl0eVNjcmlwdC5SIikKYGBgClRoZSBkYXRhIGFxdWlzdGlvbiBjb2RlIGJlbG93IGlzIG5vdCBydW4gaW4gdGhpcyBub3RlYm9vay4gSXQgd29uJ3QgdGFrZSB2ZXJ5IGxvbmcsIGJ1dCB0aGUgZGF0YSBvYmplY3RzIGhhdmUgYmVlbiBzYXZlZCBhIGxvbmcgdGhlIHdheSwgYW5kIHNvIGZvciBzaW1wbGljaXR5LCB0aGUgY29kZSBpdCBzaG93biBieSB0aGUgZGF0YSBvYmplY3RzIGFyZSBsb2FkZWQgaW4gdGhlIGRhdGEgY2xlYW5pbmcgYW5kIGFuYWx5c2lzICh3aGljaCBkb24ndCB0YWtlIHZlcnkgbG9uZyB0byBydW4sIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiB0U05FKQoKYGBge3IgZ2V0RGF0YSwgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZXM9RkFMU0V9CgojIGluaXRpYWwgcXVlcnksIGZpZ3VyZSBvdXQgaG93IG1hbnkgYXJ0aWNsZXMgdGhlcmUgYXJlCiMgcXVlcnkgd2FzIHJ1biBvbiBUaHVyc2RheSBKdWx5IDEzdGgsIDIwMTcgYXQgMTc6MTUgUERUCgpxdWVyeTE8LScoZ2Vub21lIEFORCAob3V0YnJlYWsgT1IgcGFuZGVtaWMgT1IgZXBpZGVtaWMpKSBPUiAiZ2Vub21pYyBlcGlkZW1pb2xvZ3kiJwpyZXNRMSA8LSBFVXRpbHNTdW1tYXJ5KHF1ZXJ5MSwgdHlwZT0nZXNlYXJjaCcsIGRiPSdwdWJtZWQnKQoKcmVzUTEgPC0gRVV0aWxzU3VtbWFyeShxdWVyeTEsIHR5cGU9J2VzZWFyY2gnLCBkYj0ncHVibWVkJyxyZXRtYXg9cmVzUTFAY291bnQpICM5NzE1IGFydGljbGVzCgojc2Vjb25kIHF1ZXJ5CnF1ZXJ5MjwtJyhnZW5vbWljIGVwaWRlbWlvbG9neSBPUiBtb2xlY3VsYXIgZXBpZGVtaW9sb2d5KSBBTkQgKGJhY3RlcmkqIE9SIHZpciogT1IgcGF0aG9nZW4pIEFORCBHZW5vbWUnIApyZXNRMiA8LSBFVXRpbHNTdW1tYXJ5KHF1ZXJ5MiwgdHlwZT0nZXNlYXJjaCcsIGRiPSdwdWJtZWQnKQpyZXNRMiA8LSBFVXRpbHNTdW1tYXJ5KHF1ZXJ5MiwgdHlwZT0nZXNlYXJjaCcsIGRiPSdwdWJtZWQnLHJldG1heD1yZXNRMkBjb3VudCkgIwoKI2NvbW1vbiBlbGVtZW50cyBiZXR3ZWVuIHF1ZXJpZXMKaW50ZXJzZWN0KHJlc1ExQFBNSUQscmVzUTJAUE1JRCkgJT4lIGxlbmd0aCgpCnNldGRpZmYocmVzUTFAUE1JRCxyZXNRMkBQTUlEKSAlPiUgbGVuZ3RoKCkKc2V0ZGlmZihyZXNRMkBQTUlELHJlc1ExQFBNSUQpICU+JSBsZW5ndGgoKQoKcG1pZFVuaXF1ZTwtYyhyZXNRMkBQTUlELHJlc1ExQFBNSUQpICU+JSB1bmlxdWUoKSAjMjAsNzAyIGFydGljbGVzCgpgYGAKClRoZSB0d28gcXVlcmllcyB5ZWlsZCBzbGlnaHRseSBkaWZmZXJlbnQgZGF0YXNldHMuIEl0IG1heSBiZSB0aGF0IGJlY2F1c2UgdGhlIGZpcnN0IHF1ZXJ5IGlzIG1vcmUgc3BlY2lmaWMgYW5kIHJlbGF0aW5nIHRvIG91dGJyZWFrcywgaXQgd291bGQgbm90IGhhdmUgc29tZSBvZiB0aGUgc2FtZSBjb250ZW50IGFzIHRoZSBzZWNvbmQgcXVlcnkuIEFsdGhvdWdoLCBpbiBwbGF5aW5nIGFyb3VuZCB3aXRoIG11bHRpcGxlIHF1ZXJ5IG9wdGlvbnMsIGl0IHNlZW1zIHRoYXQgZGlmZmVyZW50IHNlYXJjaCB0ZXJtcyBjYW4gcmV0dXJuIGRpZmZlcmVudCBzZXRzLiBGb3Igc2ltcGxpY2l0eSBJIHdpbGwgc3RpY2sgd2l0aCB0aGVzZSB0d28uIAoKYGBge3IgZ2V0RGF0YUZyb21RdWVyaWVzLCBldmFsPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlcz1GQUxTRX0KI25lZWQgdG8gcHV0IGV2ZXJ5dGhpbmcgdG9nZXRoZXIgaW4gYmF0Y2hlcywgMjAwMCBhcnRpY2xlcyB3YXMgdGhlIG1hZ2ljIG51bWJlciBiZWZvcmUgdGltZSBvdXRzIG9jY3VyCm51bVZhbHM8LXNlcShmcm9tPTAsdG89bGVuZ3RoKHBtaWRVbmlxdWUpLGJ5PTIwMDApCm51bVZhbHM8LWMobnVtVmFscyx0YWlsKG51bVZhbHMsIG49MSkgKyAobGVuZ3RoKHBtaWRVbmlxdWUpICUlIDIwMDApKQoKZ2VuRXBpRGF0YTwtYygpCmZvcihpIGluIDE6KGxlbmd0aChudW1WYWxzKS0xKSl7CiAgICAjdG8gbWFrZSB0aGlzIGZhc3RlciwgZm9ybSBuZXcgcXVlcnkgb24gSUQgcnVuIGluIHBhcmFsbGVsCiAgICBzdGFydCA9IG51bVZhbHNbaV0rMQogICAgZW5kID0gbnVtVmFsc1tpICsgMV0KICAgIGdlbkVwaURhdGEgPC0gcmJpbmQoZ2VuRXBpRGF0YSxmb3JtYXREYXRhKHBtaWRVbmlxdWVbc3RhcnQ6ZW5kXSkpCn0KYGBgCgpXZSBjYW4gY2FsY3VsYXRlIHRoZSBkaXN0cmlidXRpb24gb2YgcHVibGljYXRpb24gZGF0ZXMgZm9yIHRoZSB2YXJpb3VzIGFydGljbGVzOgoKYGBge3IgZ2V0RGF0YVBsb3RZZWFycywgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZXM9RkFMU0V9Cgp5ZWFyQ291bnQ8LWdlbkVwaURhdGEgJT4lCiAgZ3JvdXBfYnkoWWVhclB1YikgJT4lCiAgY291bnQoKSAlPiUKICBzZWxlY3QoWWVhclB1YixuKQoKZ2dwbG90KGdlbkVwaURhdGEsIGFlcyh4PVllYXJQdWIpKSArCiAgZ2VvbV9iYXIoKSsKICB0aGVtZV9idygpCmBgYAoKTWFueSBvZiB0aGUgYXJ0aWNsZXMgYXJlIHB1Ymxpc2hlZCBpbiB0aGUgMjAwMCdzIHdoZW4gbmV4dCBnZW5lcmF0aW9uIHNlcXVjZW5jaW5nIHdhcyBpbnRyb2R1Y2VkLiBJIGFtIGdvaW5nIHRvIHN0aWNrIHdpdGggYXJ0aWNsZXMgcHVibGlzaGVkIGZyb20gMjAwMCBvbndhcmRzLCBhbmQgSSB3aWxsIGFsc28gZmluZCB3aGljaCBhcnRpY2xlcyBtYXkgaGF2ZSBmdWxsIHRleHQgYXZhaWxhYmxlIGJ5IGxpbmtpbmcgdG8gdGhlaXIgUE1DIElELgogCmBgYHtyIGdldERhdGFBZGRQTUlDLCBldmFsPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlcz1GQUxTRX0KCmdlbkVwaURhdGE8LWdlbkVwaURhdGEgJT4lIGZpbHRlcihZZWFyUHViID4gMTk5OSkKCiN3aWxsIGNvbnZlcnQgc29tZSBQdWJtZWQgSURTIHRvIFBNQyBJRHMgaW4gb3JkZXIgdG8gZ3JhYiBzb21lIGZ1bGwgdGV4dHMgbGF0ZXIKI2kndmUgYWxyZWFkeSByZWFkIGFuZCBzYXZlZCB0aGlzIGRhdGEgZmlsZSBmb3IgYW4gZWFybGllciBhbmFseXNpcwoKaWRDb252PC1yZWFkUkRTKGZpbGU9Ii4uL2RhdGEvUE1DLWlkcy5SRFMiKQpjb252SXRlbXM8LWlkQ29udiAlPiUKICBmaWx0ZXIoUE1JRCAlaW4lIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGdlbkVwaURhdGEkUE1JRCkpKSAlPiUKICBzZWxlY3QoUE1JRCxQTUNJRCxET0kpICU+JQogIG11dGF0ZShQTUlEID0gZmFjdG9yKFBNSUQpKQoKZ2VuRXBpRGF0YTwtZnVsbF9qb2luKGdlbkVwaURhdGEsY29udkl0ZW1zKQogCiNzYXZlIHRoZSBkYXRhIHNldApzYXZlUkRTKGZpbGUgPSAiZGF0YS9nZW5FcGlEYXRhX2luaXRpYWxRdWVyeTIuUkRTIixnZW5FcGlEYXRhKSAjYWJvdXQgZm91ciBhcnRpY2xlcyBjb3VsZCBub3QgYmUgcXVlcmllZCwgdG90YWwgPSAxNzk4OCBhcnRpY2xlcwpgYGAKCiMgQ2xlYW5pbmcgdGhlIGRhdGEKCiMjIFByZXBhcmUgZGF0YSB1c2luZyB0aWR5IHRleHQKQ2xlYW5pbmcgdGhlIGRhdGFzZXQgdXNlcyBndWlkYW5jZSBmcm9tIHRoZSB0aWR5dGV4dCBvbmxpbmUgZ3VpZGUsIGl0J3MgdmVyeSB1c2VmdWwgYW5kIHdvcnRoIGNoZWNraW5nIG91dC4gSSd2ZSBhbHNvIGFkZGVkIGEgZmV3IG1vcmUgZmlsdGVycyBhbmQgc3RlbW1lZCB0aGUgdGVybXMgdXNpbmcgUG9ydGVyJ3MgYWxvZ29yaXRobS4KCmBgYHtyIHRpZHlUZXh0UHJlcCwgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZXM9RkFMU0V9CiNsb2FkaW5nIHRoZSBkYXRhc2V0IGFzIHByZXZpb3VzIGNvZGUgd2FzIG5vdCBhY3R1YWxseSBydW4gKHRha2VzIHRvbyBsb25nKQpnZW5FcGlEYXRhPC1yZWFkUkRTKGZpbGU9Ii4uL2RhdGEvZ2VuRXBpRGF0YV9pbml0aWFsUXVlcnkyLlJEUyIpCgojY29tbW9ubHkgdXNlZCB3b3JkcyBpbiB0aGUgZW5nbGlzaCBsYW5ndWFnZQpkYXRhKHN0b3Bfd29yZHMpCgojcmVtb3ZlIHNvbWUgY29tbW9uIHRlcm1zIHRoYXQgd2lsbCBvY2N1ciBpbiBhYnN0cmFjdApjdXN0b21TdG9wVGVybXM8LWRhdGEuZnJhbWUod29yZD1jKCJhYnN0cmFjdCIsICJ0ZXh0IiwgImFic3RyYWN0dGV4dCIsImludHJvZHVjdGlvbiIsImJhY2tncm91bmQiLCJtZXRob2QiLCJtZXRob2RzIiwibWV0aG9kb2xvZ3kiLCJjb25jbHVzaW9uIiwiY29uY2x1c2lvbnMiLCJvYmplY3RpdmVzIiwicmVzdWx0cyIsInJlc3VsdCIsIndlIiwibWF0ZXJpYWxzIiwicHVycG9zZSIsInNpZ25pZmljYW5jZSIsInNpZ25pZmljYW50IiwibWciKSkKCmdlbkVwaXRleHRfZGYgPC0gZ2VuRXBpRGF0YVssYygiUE1JRCIsIlRpdGxlIiwiQWJzdHJhY3QiKV0gJT4lCiAgbXV0YXRlKHRleHQgPSBwYXN0ZTAoVGl0bGUsQWJzdHJhY3QpKSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIG11dGF0ZSh3b3JkID0gc3Ryc3BsaXQoYXMuY2hhcmFjdGVyKHdvcmQpLCAiXFwuIikpICU+JSAjc29tZSB0ZXh0IGlzIHN0dWNrIHRvZ2V0aGVyIGZvciBleGFtcGxlIHBlcnNvbi5NRVRIT0RTIHNvLCBJIGFtIGZpeGluZyB0aGF0CiAgdGlkeXI6OnVubmVzdCh3b3JkKSAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQogIGFudGlfam9pbihjdXN0b21TdG9wVGVybXMpICU+JQogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQpPjIpICU+JSAjb25seSBrZWVwcyB3b3JkcyB3aXRoIGxlbmd0aCBvZiAyIG9yIGdyZWF0ZXIgKEFNUiwgYSB1c2VmdWwgYWJicmV2aWF0aW9uLCBpcyB0aHJlZSBjaGFyYWN0ZXJzIGxvbmcpICU+JQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCJcXGQiKSkgJT4lICNnZXQgcmlkIG9mIGFueSBudW1iZXJzCiAgbXV0YXRlKHdvcmRTdGVtbWVkID0gd29yZFN0ZW0od29yZCkpICU+JSAjZmluYWxseSwgZ2V0IHRoZSB3b3JkIHN0ZW1zIChQb3J0ZXIncyBhbGdvcml0aG0pCiAgc2VsZWN0KFBNSUQsd29yZCx3b3JkU3RlbW1lZCkKYGBgCgojIyBGaWx0ZXJpbmcgb3V0IHRlcm1zIHRoYXQgb2NjdXIgdG9vIGZyZXF1ZW50bHkgJiB0b28gaW5mcmVxdWVudGx5CgpIZXJlIEkgY2FsY3VsYXRlIHRoZSB0ZXJtLWZyZXF1ZW5jeSBpbnZlcnNlIGRvY3VtZW50IGZyZXF1ZW5jeSBtZXRyaWMgZm9yIGVhY2ggaW5kaXZpZHVhbCB3b3JrIGluIHRoZSBkYXRhc2V0LiBUaGVuIEkgZmlsdGVyIG91dCB3b3JkcyB0aGF0IG9jY3VyIHZlcnkgaW5mcmVxdWVudGx5IG9yIGluIG5lYXJseSBhbGwgb2YgdGhlIGRvY3VtZW50cy4gSSBoYXZlIG1hZGUgYW4gYXJiaXRyYXJ5IGNob2ljZSB0byBub3Qga2VlcCB0ZXJtcyB0aGF0IG9jY3VyIGluIGZld2VyIHRoYW4gMS4wICUgb2YgZG9jdW1lbnRzIG9yIGdyZWF0ZXIgdGhhbiA3MCUgb2YgZG9jdW1lbnRzLiBJIGRvIHdhbnQgY29hcnNlciwgYXMgb3Bwb3NlZCB0byBmaW5lciwgY2x1c3RlcnMgYmVjYXVzZSBteSBpbnRlbnRpb24gaXMgdG8gZG8gYSBzdWJzZXQgYW5hbHlzaXMgd2l0aGluIGNsdXN0ZXJzIG9uY2UgSSBrbm93IHdoYXQgdGhleSBhcmUuIEEgd29yZCB0aGF0IGlzIHByZXNlbnQgaW4gYWxtb3N0IH4xNzUgZG9jdW1lbnRzIGlzIGEgcHJldHR5IGltcG9ydGFudCB0ZXJtLCBidXQgd2l0aCBuZWFybHkgMTgsMDAwIGRvY3VtZW50cywgSSB3b3VsZCBwcmVmZXIgY2x1c3RlcnMgd2l0aCBtZW1iZXJzaGlwIG9mIHJvdWdobHkgMjAwIG9yIHRvIG1ha2UgaXQgbWVhbmluZ2Z1bCBmb3IgbXkgbGFyZ2VyIGdvYWxzLgoKYGBge3IgdGlkeVRleHRGaWx0ZXIsIGV2YWw9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2VzPUZBTFNFfQojZm9yIHRoZSBmdXR1cmUsIHN0b3JlIHRoZSB0cmFuc2Zvcm1hdGlvbnMgdGhhdCBhIHNpbmdsZSB3b3JkIGNhbiB0YWtlIG9uIGFmdGVyIHN0ZW1taW5nCiN3b3JkVG9TdGVtbWVkPC1nZW5FcGl0ZXh0X2RmICU+JSBzZWxlY3Qod29yZCx3b3JkU3RlbW1lZCkgJT4lIHVuaXF1ZSgpCgojIEkgd2lsbCBub3cgYWxzbyBhZGQgdGhlIHRlcm0gZnJlcXVlbmN5IGRvY3VtZW50IGZyZXF1ZW5jeSB2YWx1ZXMuCmdlbkVwaXRleHRfZGY8LWdlbkVwaXRleHRfZGYgJT4lCiAgY291bnQoUE1JRCwgd29yZFN0ZW1tZWQsIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYmluZF90Zl9pZGYod29yZFN0ZW1tZWQsIFBNSUQsIG4pCgp0b3RhbEFydGljbGVzPC1ucm93KGdlbkVwaURhdGEpCgp3b3JkVG9SZW1vdmU8LWdlbkVwaXRleHRfZGYgJT4lCiAgZ3JvdXBfYnkod29yZFN0ZW1tZWQpICU+JQogIGNvdW50KCkgJT4lCiAgZmlsdGVyKG5uIDwgdG90YWxBcnRpY2xlcyowLjAxIHwgbm4gPiB0b3RhbEFydGljbGVzKjAuNykKCmdlbkVwaXRleHRfZGY8LWFudGlfam9pbihnZW5FcGl0ZXh0X2RmLHdvcmRUb1JlbW92ZSxieT0id29yZFN0ZW1tZWQiKSAjdGhlcmUgYXJlIDExNjcgdW5pcXVlIHRlcm1zIHRoYXQgbWVldCB0aGlzIGNyaXRpZXJhCgojc2F2aW5nIHRoZSB0aWR5dGV4dCBkYXRhc2V0CnNhdmVSRFMoZmlsZT0iZGF0YS9nZW5FcGlEYXRhX2luaXRpYWxRdWVyeTJfdGlkeURhdGEuUkRTIixnZW5FcGl0ZXh0X2RmKQpgYGAKCiMjIENyZWF0aW5nIGEgYmlncmFtIGRhdGFzZXQKCkluIHRoaXMgYW5hbHlzaXMsIEkndmUgcmVhc29uZWQgdGhhdCBzaW5nbGUgdGVybXMgd2lsbCBwcm92aWRlIG1vcmUgZ2VuZXJhbCBjbHVzdGVyaW5nIChmb3IgZXhhbXBsZSwgSSB3b3VsZCB3YW50IGFsbCBhcnRpY2xlcyByZWxhdGVkIHRvIHR1YmVyY3Vsb3Npcywgb3IgZHJ1ZyByZXNpc3RhbmNlIHRvIGJlIGEgc2luZ2xlIGNsdXN0ZXIpLCBidXQgYmlncmFtcyBjYW4gYmUgdXNlZnVsIHRvIHJlc29sdmUgbW9yZSBmaW5lZCBncmFpbmVkIHN0cnVjdHVyZWQuIAoKYGBge3IgYmlncmFtcywgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZXM9RkFMU0V9CiBiaWdyYW1fZGYgPC0gZ2VuRXBpRGF0YVssYygiUE1JRCIsIlRpdGxlIiwiQWJzdHJhY3QiKV0gJT4lCiAgbXV0YXRlKHRleHQgPSBwYXN0ZTAoVGl0bGUsQWJzdHJhY3QpKSAlPiUKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgdGV4dCx0b2tlbiA9ICJuZ3JhbXMiLG49MikgJT4lCiAgI2NsZWFuaW5nIHVwIGVhY2ggYmlncmFtCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JQogIG11dGF0ZSh3b3JkMSA9IHN0cnNwbGl0KGFzLmNoYXJhY3Rlcih3b3JkMSksICJcXC4iKSkgJT4lCiAgdGlkeXI6OnVubmVzdCh3b3JkMSkgJT4lCiAgbXV0YXRlKHdvcmQyID0gc3Ryc3BsaXQoYXMuY2hhcmFjdGVyKHdvcmQyKSwgIlxcLiIpKSAlPiUKICB0aWR5cjo6dW5uZXN0KHdvcmQyKSAlPiUgCiAgZmlsdGVyKCEod29yZDEgJWluJSBjKHN0b3Bfd29yZHMkd29yZCxjdXN0b21TdG9wVGVybXMkd29yZCkpKSAlPiUKICBmaWx0ZXIoISh3b3JkMiAlaW4lIGMoc3RvcF93b3JkcyR3b3JkLGN1c3RvbVN0b3BUZXJtcyR3b3JkKSkpICU+JQogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQxKT4yKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZDEsIlxcZCIpKSAlPiUgCiAgZmlsdGVyKHN0cl9sZW5ndGgod29yZDIpPjIpICU+JQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkMiwiXFxkIikpICU+JQogIG11dGF0ZSh3b3JkMSA9IHdvcmRTdGVtKHdvcmQxKSkgJT4lCiAgbXV0YXRlKHdvcmQyID0gd29yZFN0ZW0od29yZDIpKSAlPiUKICB1bml0ZShiaWdyYW0sd29yZDEsd29yZDIpICU+JSAKICBzZWxlY3QoUE1JRCxiaWdyYW0pCgojY2FsY3VsYXRlIHRmX2lkZgpiaWdyYW1fZGY8LWJpZ3JhbV9kZiAlPiUKICBjb3VudChQTUlELCBiaWdyYW0sIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYmluZF90Zl9pZGYoYmlncmFtLCBQTUlELCBuKQoKc2F2ZVJEUyhmaWxlPSJkYXRhL2dlbkVwaURhdGFfaW5pdGlhbFF1ZXJ5Ml9CSUdSQU1fdGlkeURhdGEuUkRTIixiaWdyYW1fZGYpCgpgYGAKCgojQW5hbHlzaXMKCk1vc3Qgb2YgdGhpcyBhbmFseXNpcyBjYW4gYmUgYmUgcnVuIG9uIGEgbGFwdG9wIHdpdGggOCBHQiBvZiBSQU0uIEtlZXAgYW4gZXllIG91dCB3aGV0aGVyIG9yIG5vdCBjaHJvbWUgaXMgb3BlbiBiZWNhdXNlIGl0IHRha2VzIHVwIGEgbG90IG9mIG1lbW9yeSBhbmQgSSd2ZSBvZnRlbiBoYWQgdG8gY2xvc2UgaXQgYW4gb3RoZXIgYXBwbGljYXRpb25zIHRvIHJ1biB0aGlzIGFuYWx5c2lzLiBPbmUgZXhwZWN0aW9uIGlzIHRoZSBpbml0aWFsIHRTTkUgY2x1c3RlcmluZyB3aXRoIH4xODAwMCBhcnRpY2xlcywgd2hpY2ggcmVxdWlyZXMgYSBzZXJ2ZXIgd2l0aCBtb3JlIFJBTSB0byBydW4uIEkndmUgcHJvdmlkZWQgdGhlIGFuYWx5c2lzIGNvZGUgaGVyZSwgYnV0IGxvYWQgYSBwcmV2aW91c2x5IHNhdmVkIHRTTkUgQ2x1c3RlcmVkIGRhdGFzZXQgZm9yIHRoZSByZW1haW5kZXIgb2YgdGhlIGFuYWx5c2lzLiAKCkFzIGEgbm90ZSwgdGhlIHBvaW50IG9mIHRoaXMgYW5hbHlzaXMgaXMgbm90IGZvciB0aGUgYWxnb3JpdGhtcyB0byBkcml2ZSBldmVyeXRoaW5nLiBJdCdzIGFjdHVhbGx5IGZvciB0aGUgYWxnb3JpdGhtcyB0byBwcm92aWRlIHNvbWUgc3RydWN0dXJlIHRoYXQgSSB1c2VkIHRvIG1ha2UgYW5hbHl0aWMgZGVjaXNpb25zIGFib3V0IG9yIGV2ZW4gcmV2aXNlLiBJbiB0aGlzIHNlbnNlLCB0aGUgcG9pbnQgaXMgbW9yZSB0byB3b3JrIHRvZ2V0aGVyIHdpdGggdGhlIGFsZ29yaXRobSB0aGVuIGRlZmF1bHQgc29sZXkgb24gbXkganVkZ2VtZW50J3Mgb3IgdGhlIG1ldGhvZHMuIFBsYWNlcyB3aGVyZSBzdWJqZWN0aXZlIGRlY2lzaW9ucyBoYXZlIGJlZW4gbWFkZSBhcmUgY2xlYXJseSBpbmRpY2F0ZWQuIAoKIyMgU2luZ2xlIHRlcm0gY2x1c3RlciBhbmFseXNpcwpGb3IgYW5hbHlzaXMsIEkgd2lsbCBjb25zdHJ1Y3QgYSBkb2N1bWVudCB0ZXJtIG1hdHJpeCBmcm9tIHRoZSBzaW5nbGUgdGVybSBkYXRhc2V0LCBhbmQgdGhlbiBJIHdpbGwgYXBwbHkgdGhlIHRTTkUgYWxnb3JpdGhtIHRvIGNsdXN0ZXIgZGF0YSwgYW5kIHRoZSBIQkRTQ0FOIGFsZ29yaXRobSB0byBkZXJpdmVkIGNsdXN0ZXJzLiAKCgojIyMgQ3JlYXRpbmcgdGhlIGRvY3VtZW50IHRlcm0gbWF0cml4ClRoZSBkb2N1bWVudCB0ZXJtIGZyZXF1ZW5jeSB1c2VzIHRoZSB0Zl9pZGYgbWV0cmljIHRvIGFzc2VzcyB0ZWggZGlzdHJpYnV0aW9uIG9mIHRlcm1zIChjb2x1bW5zKSBhY3Jvc3MgZG9jdW1lbnRzIChyb3dzKS4gVGhpcyB3aWxsIGJlIHByb3ZpZGVkIHRvIHRoZSB0c25lIGFsZ29yaXRobSB0byBjbHVzdGVyIGRvY3VtZW50cy4KCmBgYHtyIGR0bVByZXAsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZz1GQUxTRX0KCiNsb2FkIGRhdGEsIHNpbmNlIFJNRG5vdGUgZG9lcyBub3QgcnVuIHRoZSBlYXJsaWVyIHN0ZXBzCmdlbkVwaXRleHRfZGY8LXJlYWRSRFMoZmlsZT0iZGF0YS9nZW5FcGlEYXRhX2luaXRpYWxRdWVyeTJfdGlkeURhdGEuUkRTIikKCiNub3cgY3JlYXRlIGR0bQpkdG08LWdlbkVwaXRleHRfZGYgJT4lCiAgc2VsZWN0KFBNSUQsd29yZFN0ZW1tZWQsdGZfaWRmKSAlPiUKICBzcHJlYWQoUE1JRCx0Zl9pZGYpCgpjb2xWYWxzPC1kdG0kd29yZFN0ZW1tZWQKcm93VmFsczwtY29sbmFtZXMoZHRtKVsyOm5jb2woZHRtKV0KCmR0bTwtYXMubWF0cml4KGR0bVssMjpuY29sKGR0bSldKSAlPiUgdCgpCnJvd25hbWVzKGR0bSk8LXJvd1ZhbHMKY29sbmFtZXMoZHRtKTwtY29sVmFscwoKZHRtW2lzLm5hKGR0bSldPC0wCgpzYXZlUkRTKGZpbGU9ImRhdGEvZHRtX2JpZ3JhbWluaXRpYWxRdWVyeTIuUkRTIixkdG0pCmBgYAoKIyMjIHRTTkUKCnRTTkUgdGFrZXMgYSB2ZXJ5IGxvbmcgdGltZSB0byBydW4gKGhvdXJzKS4gVGhlcmUgYXJlIHR3byB0U05FIHBhY2thZ2VzIGluIFIgKHRzbmUsIGFuZCBSdHNuZSkgd2hpY2ggaW1wbGVtZW50IHNsaWdodGx5IGRpZmZlcmVudCB2ZXJzaW9ucyBvZiB0aGUgdFNORSBhbGdvcml0aG0gKHRoZSB0U05FIHBhY2thZ2UgaXMgYWtpbiB0byB0aGUgaW5pdGlhbGx5IHB1Ymxpc2hlZCB2ZXJzaW9uKS4gVGhlIFJUc25lIHBhY2thZ2UgaXMgY29uc2lkZXJhYmx5IGZhc3RlciAoZXNwZWljYWxseSB3aGVuIHJ1biB1c2luZyBuZWFybHkgZGVmYXVsdCBwYXJhbWV0ZXJzKSwgaW4gcGFydCBiZWNhdXNlIGl0IGFsbG93cyBmb3Igc29tZSBpbmFjY3VyYWllcyB2aWEgdGhlIHRoZXRhIHBhcmFtZXRlci4gSSd2ZSB1c2VkIHByaW1hcmlseSBSdHNuZSBmb3IgYW5hbHlzaXMsIHdpdGggdGhlIGRlZmF1bHQgdGhldGEgb2YgMC41LgoKSSBoYXZlIHBsYXllZCBhcm91bmQgd2l0aCBkaWZmZXJlbnQgdmVyc2lvbnMgb2YgdFNORSBhbmQgb3B0ZWQgdG8gKGhlcmUpIG9ubHkgc2hvdyB0aGUgb25lcyBmb3IgcGVycGxleGl0eSBwYXJhbWV0ZXIgID0gMTAwICh0aGUgZGVhdWx0IGlzIGVpdGhlciAzMCBvciA1MCBkZXBlbmRpbmcgb24gdGhlIHBhY2thZ2UpLiBXaXRob3V0IGFueSBhZGRpdGlvbmFsIGFuYWx5c2lzLCB0aGlzIGlzIHdoYXQgdGhlIHJhdyB0c25lIHJlc3VsdHMgbG9vayBsaWtlOgoKYGBge3IgdHNuZUNsdXN0ZXJpbmcsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojbG9hZCBwcmUtcnVuIGFuYWx5c2lzIG9uIHNlcnZlcgpydHNuZTwtcmVhZFJEUyhmaWxlPSJkYXRhL3J0c25lX3BlcnBsZXhpdHlfMTAwLlJEUyIpCgp0c25lQ29yZDwtZGF0YS5mcmFtZShQTUlEID0gcm93bmFtZXMoZHRtKSwKICAgICAgICAgICAgICAgICAgICAgY29tcDEgPXJ0c25lJFlbLDFdLAogICAgICAgICAgICAgICAgICAgICBjb21wMiA9IHJ0c25lJFlbLDJdKQoKZ2dwbG90KHRzbmVDb3JkLGFlcyh4PWNvbXAxLHk9Y29tcDIpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjIpKwogIGxhYnMoeD0idHNuZUNvbXAxIiwKICAgICAgIHk9InRzbmVDb21wMiIpKwogIHRoZW1lX2J3KCkKCmdnc2F2ZSgiUGxhaW5Uc25lUGxvdC5wZGYiKQoKYGBgCkl0IGlzIHBvc3NpYmxlIHRvIGFscmVhZHkgc2VlIHNldmVyYWwgY2x1c3RlcnMgKG5vdGUsIHRoZSB0b3RhbCBudW1iZXIgb2YgZG9jdW1lbnRzIGhlcmUgaXMgMTc5NzQpLCB3aXRoIHNvcnQgb2Ygb25lIGxhcmdlIGdyb3VwIGluIHRoZSBjZW50cmUgKHN0aWxsIHNwbGl0IGludG8gc29tZSBjbHVzdGVycykgYW5kIG1hbnkgc21hbGxlciBjbHVzdGVycyB0byB0aGUgcGVyaXBoZXJ5LgoKIyMjIyBDbHVzdGVyIHRTTkUgcmVzdWx0cwoKVGhlIHRzbmUgcmVzdWx0cyBhcmUgY2x1c3RlcmVkIHVzaW5nIHRoZSBIREJTQ0FOIGFsZ29yaXRobS4gVGhlIG1pbm11bSBudW1iZXIgb2YgcG9pbnRzIHBlciBjbHVzdGVyIGVmZmVjdHMgaG93IHRoZSBkb2N1bWVudCBjbHVzdGVycyBhcmUgYXNzaWduZWQsIHNvIEkgdHJpZWQgYSBmZXcgZGlmZmVyZW50IGNsdXN0ZXIgc2l6ZXMgYW5kIHBsb3R0ZWQgdGhlIHJlc3VsdHMgYmVsb3cuIFRoZSBjaXJjbGVzIGluZGljYXRlIHRoZSBib3VuZHJpZXMgb2YgdGhlIGNsdXN0ZXIsIGl0J3Mgbm90ZWFibGUgaW4gZWFjaCBpbWFnZSB0aGF0IHRoZXJlIGlzIG9uZSBsYXJnZSBjaXJjbGUgdGhhdCBlbmNvbXBhc3NlcyBzbWFsbGVyIG9uZXMsIHRoaXMgaXMgdGhlICJub2lzZSIgY2lyY2xlIGFuZCBpdCBpcyBjb2xvdXJlZCBpbiBibHVlLiBJdCBpcyB0aGUgY2lyY2xlLCB3aGljaCByZXByZXNlbnQgdHJ1ZSBjbHVzdGVyaW5ncyB0aGF0IGFyZSBvZiBpbnRlcmVzdC4gCgpgYGB7ciB0c25lQ2x1c3RlclNlbGVjdCwgbWVzc2FnZT1GQUxTRSxoZWlnaHQ9MTB9CnNldC5zZWVkKDI1MykKCm1pblB0c1ZhbHM8LWMoNTAsNzUsMTAwLDEyNSwxNTAsMjUwLDUwMCwxMDAwKSAjdHJ5aW5nIG91dCBhIGJ1bmNoIG9mIGRpZmZlcmVudCB2YWx1ZXMKCmRmUHRzPC1jKCkKZm9yKG1pblB0cyBpbiBtaW5QdHNWYWxzKXsKICBjbCA8LSBoZGJzY2FuKHRzbmVDb3JkWywyOjNdLCBtaW5QdHMgPSBtaW5QdHMpICNtaW5pbXVtIGNsdXN0ZXIgc2l6ZSBvZiAxNTAgZG9jdW1lbnRzCiAgZGZQdHM8LXJiaW5kKGRmUHRzLGNiaW5kKGFzLmNoYXJhY3Rlcih0c25lQ29yZCRQTUlEKSxjbCRjbHVzdGVyLHJlcChtaW5QdHMsbnJvdyh0c25lQ29yZCkpKSkKfQoKZGZQdHM8LWFzLmRhdGEuZnJhbWUoZGZQdHMpCmNvbG5hbWVzKGRmUHRzKTwtYygiUE1JRCIsImNsdXN0ZXIiLCJncm91cGluZ0xldmVsIikKCmRmUHRzPC1pbm5lcl9qb2luKGRmUHRzLHRzbmVDb3JkKQoKZGZQdHMkZ3JvdXBpbmdMZXZlbDwtZmFjdG9yKGRmUHRzJGdyb3VwaW5nTGV2ZWwsYyg1MCw3NSwxMDAsMTI1LDE1MCwyNTAsNTAwLDEwMDApKQoKZGZQdHMgJT4lCiAgbXV0YXRlKGlzTm9pc2UgPSBpZmVsc2UoY2x1c3Rlcj09MCwiTm9pc2UiLCJTaWduYWwiKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWNvbXAxLHk9Y29tcDIpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjEpKwogIHRoZW1lX2J3KCkrCiAgZmFjZXRfd3JhcCh+Z3JvdXBpbmdMZXZlbCkgKwogIHN0YXRfZWxsaXBzZShhZXMoZ3JvdXA9Y2x1c3Rlcixjb2w9aXNOb2lzZSkpKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoImJsdWUiLCJyZWQiKSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJOb25lIikKCmdnc2F2ZShmaWxlPSJvcHRpbWFsUGFyYW1TZWFyY2gucGRmIikKCmBgYApBcyB0aGUgc2NhbGluZyBmYWN0b3IgZXhjZWVkcyAxNTAsIHRoZSBkYXRhIGdyb3VwIGludG8gZmV3ZXIgY2x1c3RlcnMgdW50aWwgZXZlcnl0aGluZyBhcHBlYXJzIHRvIGJlIG5vaXNlLiBUaGUgc3dlZXQgc3BvdCBpcyBhIG1pbiBwdHMgdmFsdWUgc29tZXdoZXJlIGJldHdlZW4gNTAgYW5kIDE1MCwgZWFjaCB3aXRoIHJhdGhlciBkaWZmZXJlbnQgbnVtYmVyIG9mIGNsdXN0ZXJzOgoKYGBge3IgdHNuZUNsdXN0ZXJTaXplLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmRmUHRzICU+JQogIHNlbGVjdChncm91cGluZ0xldmVsLGNsdXN0ZXIpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgZ3JvdXBfYnkoZ3JvdXBpbmdMZXZlbCkgJT4lCiAgY291bnQoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Z3JvdXBpbmdMZXZlbCx5PW4pKSsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKwogIHlsYWIoIk51bWJlciBvZiBDbHVzdGVyaW5ncyIpKwogIHhsYWIoIm1pblB0cyB2YWx1ZSIpKwogIGdlb21fdGV4dChhZXMobGFiZWw9biksbnVkZ2VfeSA9IDEsY29sPSJyZWQiKSsKICB0aGVtZV9idygpCgpgYGAKClRoZSBmaW5hbCBkZWNpZGluZyBmYWN0b3Igd2lsbCBiZSB0aGUgbW9zdCBjb21tb24gdGVybSBpbiBlYWNoIG9mIHRoZSBjbHVzdGVycy4gSWYgc29tZSBjbHVzdGVycyBoYXZlIGEgY29tbW9uIHRlcm0gcmVwZWF0aW5nIGFjcm9zcyBkaWZmZXJlbnQgY2x1c3RlcnMgKGZvciBleGFtcGxlLCBpZiBISVYgb2NjdXJzIGFjcm9zcyBtdWx0aXBsZSBjbHVzdGVycyksIHRoYW4gSSB3b3VsZCBjb25zaWRlciB0aGlzIHRvIGJlIHRvbyBmaW5lIGdyYWluZWQgc2VwYXJhdGlvbi4KCmBgYHtyIHRzbmVDbHVzdGVyVG9waWNzLCBtZXNzYWdlPUYsd2FybmluZz1GfQojZm9yIGVhY2ggZ3JvdXBpbmcgbGV2ZWwsIGFuZCBlYWNoIGNsdXN0ZXIgaWRlbnRpZnkgdGhlIG1vc3QgY29tbW9uIHRlcm0KZ2V0VG9wVGVybXM8LWZ1bmN0aW9uKGNsdXN0UE1JRCx0b3BOVmFsPTEsY2x1c3RWYWx1ZSA9IE5BKXsKICBjbHVzdFZhbHVlPC1jbHVzdFZhbHVlWzFdCiAgCiAgaWYoY2x1c3RWYWx1ZSA9PSAwKQogICAgcmV0dXJuKCJOb2lzZSIpCiAgCiAgdG9wV29yZDwtZ2VuRXBpdGV4dF9kZiAlPiUKICAgIGZpbHRlcihQTUlEICVpbiUgY2x1c3RQTUlEKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIGdyb3VwX2J5KHdvcmRTdGVtbWVkKSAlPiUKICAgIHRhbGx5KCkgJT4lCiAgICBhcnJhbmdlKC1ubikgJT4lCiAgICB0b3Bfbih0b3BOVmFsKQogIAogICMgcmV0dXJuIGNoYXJhY3RlciBhbmQgY29sbGFwc2UgdG9wIHRlcm1zICh1c2VmdWwgaW4gZXZlbiBvZiBhIHRpZSkKICB0b3BXb3JkPC1wYXN0ZTAodG9wV29yZCR3b3JkU3RlbW1lZCxjb2xsYXBzZSA9ICItIikKICByZXR1cm4odG9wV29yZCkKfQoKY2x1c3RUb3BpY3M8LWRmUHRzICU+JQogIGdyb3VwX2J5KGdyb3VwaW5nTGV2ZWwsY2x1c3RlcikgJT4lCiAgbXV0YXRlKHRvcFRlcm1zID0gZ2V0VG9wVGVybXMoUE1JRCxjbHVzdFZhbHVlPWNsdXN0ZXIpKQoKIyBhIHNtYWxsIHNhbml0eSBjaGVjayB0byBjb25maXJtIHRoaXMgYWN0dWFsbHkgd29ya2VkCmNsdXN0UE1JRDwtZGZQdHMgJT4lIGZpbHRlcihncm91cGluZ0xldmVsID09IDUwKSAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMTQpICU+JSBzZWxlY3QoUE1JRCkKZ2V0VG9wVGVybXMoY2x1c3RQTUlEJFBNSUQsY2x1c3RWYWx1ZT0xNCkKCmdlbkVwaXRleHRfZGYgJT4lCiAgZmlsdGVyKFBNSUQgJWluJSBjbHVzdFBNSUQkUE1JRCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGNvdW50KHdvcmRTdGVtbWVkKSU+JQogIGFycmFuZ2UoLW5uKQoKIyBeIHllcywgaXQgYWN0dWFsbHkgd29ya3MKCiNub3csIHNlZSBpZiB0aGUgc2FtZSB0ZXJtcyBvY2N1ciBhY3Jvc3MgZGlmZmVyZW50IHRvcGljcwpjbHVzdFRvcGljcyAlPiUKICBzZWxlY3QodG9wVGVybXMsZ3JvdXBpbmdMZXZlbCxjbHVzdGVyKSAlPiUKICB1bmlxdWUoKSAlPiUKICBmaWx0ZXIoZ3JvdXBpbmdMZXZlbCAlaW4lIGMoNTAsNzUsMTAwLDEyNSwxNTApKSAlPiUKICBncm91cF9ieShncm91cGluZ0xldmVsLHRvcFRlcm1zKSAlPiUKICBjb3VudCgpICAlPiUKICBnZ3Bsb3QoYWVzKHg9dG9wVGVybXMseT1uKSkrCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTEsY29sPSJyZWQiKSsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikrCiAgZmFjZXRfd3JhcCh+Z3JvdXBpbmdMZXZlbCxuY29sPTEpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsaGp1c3Q9MSx2anVzdD0wLjUpKQpgYGAKCk1vc3QgdG9wIHRlcm1zIHRlbmQgdG8gb2NjdXIgb25seSBpbiBvbmUgY2x1c3RlciwgYW5kIHRlbmQgdG8gYmUgaW5mZWN0aW91cyBhZ2VudHMgb3IgZGlzZWFzZXMgKGhidiwgaGN2LCBoaXYsIHR1YmVyY3Vsb3NpcykuIFN0aWxsIGF0IGxvd2VyIGxldmVscyBvZiBtaW5pbXVtIGNsdXN0ZXIgc2l6ZXMsIHNvbWUgY2x1c3RlcnMgZm9ybSBmb3Igb3RoZXIgcmVhc29ucy4gRm9yIGV4YW1wbGUsICJjaGluYSIgYW5kICJjaGlsZHJlbiIgYXJlIHRoZWlyIG93biBjbHVzdGVyLCBidXQgbmVpdGhlciBpcyBwYXJ0aWN1bGFyaWx5IHVzZWZ1bCB0byBtZSBiZWNhdXNlIG1hbnkgZGlmZmVyZW50IGRpc2Vhc2VzIGNhbiBvY2N1ciBpbiBjaGluYSAoYW5kIGluIGZhY3Qgb2NjdXIgaW4gb3RoZXIgcGFydHMgb2YgdGhlIHdvcmxkIHRvbyksIGFuZCBsaWtld2lzZSBtYW55IGRpZmZlcmVudCBkaXNlYXNlcyBvY2N1ciBpbiBjaGlsZHJlbiBhbmQgYWR1bHRzLiBBcyB0aGUgY2x1c3RlciBzaXplcyBiZWNvbWUgbGFyZ2VyIChjbG9zZXIgdG8gMTUwKSBJIChzdWJqZWN0aXZlbHkpIGFzc2VzcyB0aGF0IHRoZSBjbHVzdGVyIGJlY29tZSBtb3JlICJ1c2VmdWwiLCBmb3IgZXhhbXBsZSBjaGlsZHJlbiBjb2xsYXBzZXMgaW50byBvdGhlciBjbHVzdGVycy4gV2UgY2FuIHNob3cgdGhpcyB3aXRoIGEgc2Fua2V5IGRpYWdyYW0uIEl0IGlzIGVhc2llciB0byB2aWV3IHRoaXMgZGlhZ3JhbSB3aXRoIGEgbGFyZ2VyIG1vbml0b3IuIEl0J3MgcG9zc2libGUgdG8gdGFrZSBhIGNsb3NlciBsb29rIGF0IHRvcCAicGF0aHMiIGlmIHlvdSB3aWxsIG9mIGNsdXN0ZXJzIGFzIHRoZSBtaW5QdHMgdmFsdWUgY2hhbmdlcyAtIHNvbWUgb2YgdGhlIGxhcmdlc3QgYW5kIG1vc3QgY29uc2lzdGVudCBwYXRocyAoaS5lLiBpdGVtIHN0YXlzIGluIHRoZSBzYW1lIGNsdXN0ZXIpIGFyZSBkaXNlYXNlIGRyaXZlbi4gRm9yIGV4YW1wbGUgYW4gaXRlbSB0aGF0IGlzIGNsYXNzaWZpZWQgYXMgSElWIHRlbmRzIHRvIHN0YXkgd2l0aGluIHRoZSBISVYgY2x1c3RlciB1bnRpbCBtaW5QdHMgdmFsdWUgZ2V0cyB0b28gaGlnaC4gTW9zdCBvZiB0aGUgYXJ0aWNsZXMgYXJlIGp1c3QgIm5vaXNlIiwgbmV2ZXIgY2x1c3RlcmluZyB3aXRoIGFueXRoaW5nIHVzZWZ1bCBhdCBhbGwuIFNvbWUgcG9zaXRpdmUgdGhpbmdzIChhZ2FpbiBieSBteSBzdWJqZWN0IGFzc2Vzc21lbnQpIGlzIHRoYXQgdGhlIGxlc3MgdXNlZnVsICJjaGluYSIgY2x1c3RlciBjb2xsYXBzZXMgaW50byAic3RyYWluIiwgd2hpY2ggSSB0aGluayBpcyBtdWNoIG1vcmUgdXNlZnVsIGJlY2F1c2UgaXQgaXMgYSBtb3JlIGdlbmVyYWwgY2xhc3NpZmljYXRpb24uICAKCmBgYHtyIGNsdXN0ZXJDaGFuZ2VzLCBtZXNzYWdlcz1GQUxTRSx3YXJuaW5nPUZBTFNFLGhlaWdodD0yMH0KbGlicmFyeShhbGx1dmlhbCkKdG1wPC1jbHVzdFRvcGljcyAlPiUKICBncm91cF9ieShQTUlEKSAlPiUKICBzdW1tYXJpc2UoY2x1c3RQYXRoID0gcGFzdGUwKHRvcFRlcm1zLGNvbGxhcHNlPSI7IikpICU+JQogIGNvdW50KGNsdXN0UGF0aCkgJT4lCiAgYXJyYW5nZSgtbikKCnRtcCAlPiUKICB0b3BfbigyMCkKYGBgCgpgYGB7ciBjbHVzdGVyQ2hhbmdlc1NuYWtleSxtZXNzYWdlPUYsd2FybmluZz1GfQp0bXAyPC1zYXBwbHkodG1wJGNsdXN0UGF0aCxmdW5jdGlvbih4KXtzdHJzcGxpdCh4LCI7Iil9KSAlPiUgYmluZF9yb3dzKCkgJT4lIHQoKQp0bXAyPC1hcy5kYXRhLmZyYW1lKHRtcDIpCmNvbG5hbWVzKHRtcDIpPC1wYXN0ZSgibWluUHRzPSIsbWluUHRzVmFscykKCnRtcDIkZnJlcTwtdG1wJG4KCiNjbGVhbiBpdCB1cCBhIGJpdCwgaGlkZSBwYXRocyB3aXRoIGZld2VyIHRoYW4gMTAwIGRvY3VtZW50cwphbGx1dmlhbCh0bXAyWywxOjhdLGZyZXE9dG1wMiRmcmVxLGhpZGU9dG1wMiRmcmVxIDwgMTAwLAogICAgICAgICBjZXg9MC4zNSxnYXAud2lkdGggPSAwLGFscGhhPTAuNzUsY2V4LmF4aXM9MC43NSwKICAgICAgICAgICBjb2wgPSBpZmVsc2UodG1wMlssMV0gPT0gIk5vaXNlIiwgIm9yYW5nZSIsICJncmV5IikpCmBgYAoKSXQncyBwb3NzaWJsZSB0byBzZWUgdGhhdCBtYW55IGRvY3VtZW50cyB0aGF0IGFyZSBpbml0aWFsbHkgY2xhc3NpZmllZCBhcyBub2lzZSAoaGlnaGxpZ2h0ZWQgaW4gdGhlIG9yZ2FuZ2UgY29sb3VyKSwgcHJldHR5IG11Y2ggc3RheXMgbm9pc2Ugbm8gbWF0dGVyIHdoYXQgdGhlIHBhcmFtZXRlciB2YWx1ZXMgdG8gY2hhbmdlIHRvIHRoZSBleGNlcHRpb24gaXMgcGVyaGFwcyBhdCBtaW5QdHMgdmFsdWUgb2YgMjUwLCB3aGVyZSBldmVyeXRoaW5nIGhhcyB0aGUgdGVybSBpc29sIChmb3IgaXNvbGF0ZSkuIEl0J3MgYWxzbyBwb3NzaWJsZSB0byBzZWUgdGhlIHRyYWplY3RvciBvZiBpbmlkaXZpZHVhbCB0ZXJtcyB0aGF0IEkgdGhvdWdodCB3ZXJlIG9kZCAoZm9yIGV4YW1wbGUgY2x1c3RlcnMgd2l0aCBjaGluYSBvciBjaGlsZHJlbiksIHdoaWNoIGRvbid0IGFjdHVhbGx5IGNsdXN0ZXIgd2l0aCBhbnkgZGlzZWFzZXMsIGJ1dCBpbnN0ZWFkIGV2ZW50dWFsbHkgYmVjb21lIGNsdXN0ZXJzIHdpdGggZ2VuZXJpYyB0ZXJtcyBsaWtlICJnZW5vbSIgb3IgImlzb2xhdGUiIG9yICJzdHJhaW4iIGFuZCBtYW55IG1vcmUgZXZlbiBqdXN0IGJlY29tZSBub2lzZS4gSXQncyBwb3NzaWJsZSB0aGF0IHRoZXNlIGFyZSBnZW5lcmljIG1vbGVjdWxhciBiaW9sb2d5IHR5cGUgYXJ0aWNsZXMsIHNvIGl0IG1heSBub3QgYmUgd29ydGggZGlzY2FyZGluZyB0aGVtLiAKCkJhc2VkIHVwb24gdGhlIGFib3ZlIGFuYWx5c2lzLCBJJ3ZlIG9wdGVkIHRvIHVzZSBhIG1pblB0cyBzaXplIG9mIDE1MC4gSSdsbCB1c2UgdGhlIHN1YnNlcXVlbnQgYW5hbHlzaXMgd2l0aCB0aGUgYmlncmFtcyB0byBoZWxwIHNvcnQgb3V0IGNyb3NzLWN1dHRpbmcgdG9waWNzIGFuZCBhbHNvIHRvIHJlc3VyZWN0IHNvbWUgb3RoZXIgcGF0aG9nZW5zIHRoYXQgbWlnaHQgaGF2ZSBzbGlwcGVkIGludG8gdGhlICJpc29sIiwgImdlbm9tZSIsIG9yIGV2ZW4gIk5vaXNlIiBjYXRlZ29yaWVzLiBTbywgbm93IEkgd2lsbCBhc3NpZ24gdGhlIG9mZmljaWFsIGNsdXN0ZXJzIHRvIHRzbmVDb3JkIGFuZCB1c2UgdHNuZUNvcmQgZm9yIGFuYWx5c2lzLiBUaGUgY2x1c3RlciB2YWx1ZXMgb2YgMTUwIGdpdmVzIG1lIHRoZSBjbGVhbmVzdCBkYXRhLCBvciBzbyBJIGhhdmUgYXNzZXNzZWQsIGluIHRoYXQgZG9jdW1lbnQgY2x1c3RlciBtb3N0IGNsZWFybHkgYWNjb3JkaW5nIHRvIGRpc2Vhc2VzIG9yIHBhdGhvZ2Vucy4KCkkgd2lsbCBhbHNvIHJlbW92ZSB0aG9zZSBhcnRpY2xlcyB3aG8gYXJlIG1haW5seSBjbGFzc2lmaWVkIGFzIG5vaXNlIGFsbCB0aHJvdWdob3V0IGRpZmZlcmVudCBtaW5QdFZhbHVlcywgdGhleSBtaWdodCBjb250YWluIHNvbWV0aGluZyB1c2VmdWwsIGJ1dCBmb3IgbXkgcHVycG9zZXMgSSB3YW50IHRvIGJvb3N0IHRoZSBzaWduYWwgb2YgdXNlYWJsZSBhcnRpY2xlcyBhbmQgc28gSSd2ZSBvcHRlZCB0byByZW1vdmUgdGhlbS4gVGhpcyB3aWxsIGJlIGEgdG90YWwgb2YgMjY1OSBkb2N1bWVudHMgKG5vdGUhIFRoaXMgZG9lc24ndCBtZWFuIHRoYXQgdGhlICJOb2lzZSIgY2x1c3RlciB3aWxsIGRpc2FwcGVhciwgaXQgd2lsbCBzdGlsbCBiZSB0aGVyZSwgYnV0IGFja25vd2xlZGduZyB0aGF0IHRob3NlIGFydGljbGVzIG1heSBoYXZlIGNsdXN0ZXJlZCB1c2VmdWxseSB1bmRlciBkaWZmZXJlbnQgcGFyYW1ldGVycywgc28gdGhvc2UgcmVtYWluIGluIHRoZSBhbmFseXNpcykKCmBgYHtyIHRzbmVDbHVzdGVyLCBtZXNzYWdlPUZBTFNFLGhlaWdodD0xMH0KCnZlcnlOb2lzZXlEb2N1bWVudHM8LWNsdXN0VG9waWNzICU+JQogIGdyb3VwX2J5KFBNSUQpICU+JQogIHN1bW1hcmlzZShjbHVzdFBhdGggPSBwYXN0ZTAodG9wVGVybXMsY29sbGFwc2U9IjsiKSkgJT4lCiAgZmlsdGVyKGNsdXN0UGF0aCAlaW4lIGMoIk5vaXNlO05vaXNlO05vaXNlO05vaXNlO05vaXNlO2lzb2w7Z2Vub207Tm9pc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICJOb2lzZTtOb2lzZTtOb2lzZTtOb2lzZTtOb2lzZTtpc29sO05vaXNlO05vaXNlIikpICU+JQogIGlubmVyX2pvaW4odHNuZUNvcmQpCgpzYXZlUkRTKGZpbGU9ImRhdGEvdmVyeU5vaXNleURvY3VtZW50cy5SRFMiLHZlcnlOb2lzZXlEb2N1bWVudHMpCgp0c25lQ29yZDwtdHNuZUNvcmQgJT4lCiAgYW50aV9qb2luKHZlcnlOb2lzZXlEb2N1bWVudHMpCgojZ2V0IHRoZSBjbHVzdGVycyBmb3IgdGhlIHRzbmUgcGVycGxleGl0eSAxMDAgcGxvdCB1c2luZyBIREJTQ0FOCmNsIDwtIGhkYnNjYW4odHNuZUNvcmRbLDI6M10sIG1pblB0cyA9IDE1MCkgI21pbmltdW0gY2x1c3RlciBzaXplIG9mIDE1MCBkb2N1bWVudHMKdHNuZUNvcmQkdHNuZUNsdXN0ZXI8LWNsJGNsdXN0ZXIKCiNub3cgYXNzaWduIHRoZSBjbHVzdGVyIHRvcGljcwp0c25lQ29yZDwtdHNuZUNvcmQlPiUKICBncm91cF9ieSh0c25lQ2x1c3RlcikgJT4lCiAgbXV0YXRlKHRzbmVDbHVzdGVyTmFtZXMgPSBnZXRUb3BUZXJtcyhQTUlELGNsdXN0VmFsdWU9dHNuZUNsdXN0ZXIsdG9wTlZhbCA9IDIpKQpgYGAKCldlIGNhbiBwbG90IHRoZSBjbHVzdGVycyB0byBzZWUgd2hhdCB0aGVpciBtZW1lcmJzaGlwIGlzIGFuZCBob3cgdGhleSdyZSBkZWZpbmVkLiBGaXJzdCB3ZSdsbCBzZWUgaG93IHRoZSBjbHVzdGVycyBhcmUgYXNzaWduZWQgdXNpbmcgdGhlIHRzbmUgc3BhY2UuIEkgYW0gZ29pbmcgdG8gcmVtb3ZlIGFsbCB0aGUgYXJ0aWNsZXMgdGhhdCBoYXZlIGJlZW4gbGFiZWxsZWQgYXMgIm5vaXNlIiBieSB0aGUgSERCU0NBTiBhbGdvcml0aG0uCgpgYGB7ciB0c25lQ2x1c3Rlck1lbWJlcnNoaXBTY2F0dGVyUGxvdCwgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KI2NsdXN0ZXJOYW1lTGFiZWwKY2x1c3Rlck5hbWVzIDwtIHRzbmVDb3JkICU+JQogIGRwbHlyOjpncm91cF9ieSh0c25lQ2x1c3Rlck5hbWVzKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKG1lZFggPSBtZWRpYW4oY29tcDEpLAogICAgICAgICAgICBtZWRZID0gbWVkaWFuKGNvbXAyKSkgJT4lCiAgZmlsdGVyKHRzbmVDbHVzdGVyTmFtZXMgIT0gIk5vaXNlIikKCgojd3JpdGUgdXNpbmcgZ2VvbV9lbGxpcHNlCnRzbmVDb3JkICU+JQogIGZpbHRlcih0c25lQ2x1c3Rlck5hbWVzICE9ICJOb2lzZSIpICU+JSAKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4yLGFlcyhjb2xvdXI9dHNuZUNsdXN0ZXJOYW1lcykpKwogIHRoZW1lX2J3KCkrCiAgc3RhdF9lbGxpcHNlKGNvbD0iYmxhY2siLGFscGhhPTAuNSkrCiAgZ2VvbV90ZXh0KGRhdGE9Y2x1c3Rlck5hbWVzLGFlcyh4PW1lZFgseT1tZWRZLGxhYmVsPXRzbmVDbHVzdGVyTmFtZXMpLGNvbD0iYmxhY2siLGZvbnRmYWNlPSJib2xkIixzaXplPTMpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iTm9uZSIpCgpgYGAKCkZyb20gdGhlIGFib3ZlIGltYWdlLCB0aGUgY2x1c3RlcnMgc2VlbXMgcmF0aGVyIHJlYXNvbmJseSBhc3NpZ25lZCwgdGhlcmUgYXJlIHNvbWUgaW5zdGFuY2VzLCBidXQgSSB3YW50IHRvIGJyZWFrIGFwYXJ0IG9uZSBiaWcgY2x1c3RlciA6ICJnZW5vbS1zZXF1ZW5jZSIsIHdoaWNoIGRvZXMgbWFrZSBzbWFsbGVyIGNsdXN0ZXJzIHdoZW4gbWluUHRzID0gNzUgKGZyb20gdGhlIGFib3ZlIGFuYWx5c2lzLCA3NSBiZWluZyB0aGUgY2xvc2VzdCB2YWx1ZSB0byAxNTAgd2hlcmUgdGhlIGRhdGEgZGl2aWRlIGdlbm9tLXNlcXVlbmMgaW50byB0d28gZ3JvdXBzKSwgc28gSSB3aWxsIGJvcnJvdyB0aGF0IGNsdXN0ZXJpbmcgYnJlYWsgZG93biB0aGF0IG1pZGRsZSBncm91cCAobm90ZSB0aGlzIGlzIGFnYWluLCBhIHN1YmplY3RpdmUgZGVjaXNpb24gSSBhbSBtYWtpbmcpLgoKYGBge3IgdHNuZUNsc3V0ZXJCcmVha2Rvd24sIG1lc3NhZ2U9Rix3YXJuaW5nPUZ9CgpwbWlkVmFsczwtIGZpbHRlcih0c25lQ29yZCwgdHNuZUNsdXN0ZXJOYW1lcyA9PSAiZ2Vub20tc2VxdWVuYyIpICU+JSB1bmdyb3VwKCkgJT4lIHNlbGVjdChQTUlEKQoKbmV3Q2x1c3Q8LWNsdXN0VG9waWNzICU+JSAKICBmaWx0ZXIoUE1JRCAlaW4lIHBtaWRWYWxzJFBNSUQpICU+JQogIGZpbHRlcihncm91cGluZ0xldmVsPT03NSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIG11dGF0ZSh0b3BUZXJtcyA9IGdldFRvcFRlcm1zKFBNSUQsY2x1c3RWYWx1ZT1jbHVzdGVyLHRvcE5WYWwgPSAyKSkKCiNzb21lIHNtYWxsIGZpeGVzIGJhc2VkIHVwb24gbWFudWFsIGluc3BlY3Rpb24gKG49MSBoZXJlKQpuZXdDbHVzdFt3aGljaChuZXdDbHVzdCR0b3BUZXJtcz09ICJpbmZlY3Qtc3R1ZGkiKSwgXSR0b3BUZXJtczwtImdlbmUtY2VsbCIKbmV3Q2x1c3Rbd2hpY2gobmV3Q2x1c3QkdG9wVGVybXM9PSAiZ2VuZS1jZWxsIiksIF0kY2x1c3RlcjwtMjMKI25ld0NsdXN0W3doaWNoKG5ld0NsdXN0JHRvcFRlcm1zPT0gInZpcnVzLXNlcXVlbmMiKSwgXSR0b3BUZXJtczwtInZpcnUtc2VxdWVuYyIKCmlkeENsdXN0PC1tYXRjaChuZXdDbHVzdCRQTUlELHRzbmVDb3JkJFBNSUQpCmlkeENsdXN0PC1pZHhDbHVzdFshaXMubmEoaWR4Q2x1c3QpXQoKdHNuZUNvcmRbaWR4Q2x1c3QsXSR0c25lQ2x1c3RlcjwtbmV3Q2x1c3QkY2x1c3Rlcgp0c25lQ29yZFtpZHhDbHVzdCxdJHRzbmVDbHVzdGVyTmFtZXM8LW5ld0NsdXN0JHRvcFRlcm1zCgoKI2NsdXN0ZXJOYW1lTGFiZWwKY2x1c3Rlck5hbWVzIDwtIHRzbmVDb3JkICU+JQogIGdyb3VwX2J5KHRzbmVDbHVzdGVyTmFtZXMpICU+JQogIHN1bW1hcmlzZShtZWRYID0gbWVkaWFuKGNvbXAxKSwKICAgICAgICAgICAgbWVkWSA9IG1lZGlhbihjb21wMikpICU+JQogIGZpbHRlcih0c25lQ2x1c3Rlck5hbWVzICE9ICJOb2lzZSIpCgp0c25lQ29yZCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIodHNuZUNsdXN0ZXJOYW1lcyAhPSAiTm9pc2UiKSAlPiUgY291bnQoKSAjMTEsNDE2CiN3cml0ZSB1c2luZyBnZW9tX2VsbGlwc2UKdHNuZUNvcmQgJT4lCiAgZmlsdGVyKHRzbmVDbHVzdGVyTmFtZXMgIT0gIk5vaXNlIikgJT4lIAogIGdncGxvdChhZXMoeD1jb21wMSx5PWNvbXAyLGdyb3VwPXRzbmVDbHVzdGVyTmFtZXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjIsYWVzKGNvbG91cj10c25lQ2x1c3Rlck5hbWVzKSkrCiAgdGhlbWVfYncoKSsKICBsYWJzKHg9InRzbmVDb21wMSIsCiAgICAgICB5PSJ0c25lQ29tcDIiKSsKICBzdGF0X2VsbGlwc2UoY29sPSJibGFjayIsYWxwaGE9MC41KSsKICBnZW9tX3RleHQoZGF0YT1jbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksY29sPSJibGFjayIsZm9udGZhY2U9ImJvbGQiLHNpemU9MykrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJOb25lIikKCgpzYXZlUkRTKGZpbGU9ImRhdGEvcnRzbmVfcGVycGxleGl0eV8xMDBfd2l0aENsdXN0ZXJzLlJEUyIsdHNuZUNvcmQpCmBgYAoKYGBge3IgdHNuZUNsc3V0ZXJCcmVha2Rvd25fUGFwZXJWZXJzaW9uLCBldmFsPUZBTFNFLGVjaG89RkFMU0UsbWVzc2FnZT1GLHdhcm5pbmc9Rn0KdHNuZUNvcmQgJT4lCiAgZmlsdGVyKHRzbmVDbHVzdGVyTmFtZXMgIT0gIk5vaXNlIikgJT4lIAogIGdncGxvdChhZXMoeD1jb21wMSx5PWNvbXAyLGdyb3VwPXRzbmVDbHVzdGVyTmFtZXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjIsY29sb3VyPSIjMWI5ZTc3IikrCiAgdGhlbWVfYncoKSsKICBsYWJzKHg9InRzbmVDb21wMSIsCiAgICAgICB5PSJ0c25lQ29tcDIiKSsKICBzdGF0X2VsbGlwc2UoY29sPSJibGFjayIsYWxwaGE9MC41KSsKICBnZW9tX3RleHQoZGF0YT1jbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksY29sPSJibGFjayIsZm9udGZhY2U9ImJvbGQiLHNpemU9MykrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yPSBlbGVtZW50X2JsYW5rKCkscGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKCmdnc2F2ZSgicHJpbWFyeUNsdXN0ZXJfYnlQYXRob2dlbi5wZGYiLHVuaXQ9ImNtIix3aWR0aD0xOC41MyxoZWlnaHQ9MTEuNDMpCmBgYAoKV2UgY2FuIGFsc28gc2VlIHRoZSBzaXplIChtZW1iZXJzaGlwKSBvZiB0aG9zZSBjbHVzdGVyczoKCmBgYHtyIHRzbmVDbHVzdGVyTWVtYmVyc2hpcEJhckNoYXJ0LCBtZXNzYWdlPUYsd2FybmluZz1GfQp0c25lQ29yZCAlPiUKICBncm91cF9ieSh0c25lQ2x1c3Rlck5hbWVzKSAlPiUKICBjb3VudCgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcih0c25lQ2x1c3Rlck5hbWVzLC1uKSx5PW4pKSsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKwogIHRoZW1lX2J3KCkrCiAgeGxhYigiQ2x1c3RlciBOYW1lIikrCiAgeWxhYigiQ2x1c3RlciBTaXplIikrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCxoanVzdD0xLHZqdXN0PTAuNSkpCmBgYAoKSXQncyBub3RhYmxlIHRoYXQgbmVhcmx5IDQsMDAwIGRvY3VtZW50cyAob2YgfjE4MDAwICgyMiUpKSBjb3VsZCBub3QgYmUgY2x1c3RlcmVkIGFuZCBhcmUgY2xhc3NpZmllZCBhcyBOb2lzZS4gSW4gdG90YWwsIHRoZXJlIGFyZSAxMSw0MTYgZG9jdW1lbnRzIHRoYXQgcmVtYWluIGluIGFuYWx5c2lzLiBUaGVyZSBtYXkgc3RpbGwgYmUgdXNlZnVsIGRhdGEgaW4gdGhlcmUsIHNvIGl0IG1heSBub3QgYmUgYSBnb29kIGlkZWEgdG8gdGhyb3cgaXQgb3V0LiBIZXJlIGlzIGhvdyB0aGUgbm9pc2UgaXMgc2NhdHRlciBhbW9uZyB0aGUgZGF0YToKCmBgYHtyIHRzbmVDbHVzdGVyTm9pc2VEaXN0LCBtZXNzYWdlPUYsd2FybmluZz1GfQp0c25lQ29yZCAlPiUKICBtdXRhdGUoaXNOb2lzZSA9IGlmZWxzZSh0c25lQ2x1c3Rlck5hbWVzID09ICJOb2lzZSIsIk5vaXNlIiwiQ2x1c3RlciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzLGNvbG91cj1pc05vaXNlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbCA9IGlzTm9pc2UpLGFscGhhPTAuMikrCiAgdGhlbWVfYncoKSsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCIjMWI5ZTc3IiwiIzc1NzBiMyIpKSsKICBzdGF0X2VsbGlwc2UoKSsKICBnZW9tX3RleHQoZGF0YT1jbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksY29sPSJibGFjayIsc2l6ZT0zKQpgYGAKCkl0J3MgaW50ZXJlc3RpbmcgdG8gbm90ZSB0aGUgY29ubmVjdGlvbiBiZXR3ZWVuIHRoZSBoaXYtc3Vic3R5cGUgYW5kIHR1YmVyY3Vsb3NpcyBjb25uZWN0aW9uLiBUaG9zZSBkb2N1bWVudHMgYXJlIHJlZ2lzdGVyZWQgYXMgbm9pc2UsIGhvd2V2ZXIsIEhJViBhbmQgVEIgYXJlIGNvbW1vbiBjby1pbmZlY3Rpb25zLCBhbmQgdGhhdCBsaW5rIGJldHdlZW4gdGhlIHR3byBvZiB0aGVtIGlzIGFuIGludGVyZXN0aW5nIGJyaWRnZSBiZXR3ZWVuIHRoc2VzIHRvcGljcy4gSXQgaXMgbm90ZWFibGUgdGhhdCBvdGhlciBzdWNoIGJyaWRnZXMgYXJlIG5vdCBmb3VuZCBiZXR3ZWVuIG90aGVyIGNsdXN0ZXJzIC0gdGhpcyBjb3VsZCBiZSBiZWNhdXNlIHRoZXkgZG9uJ3QgZXhpc3Qgb3IgdGhlIHNpZ25hbCBpcyBub3Qgc3Ryb25nIGVub3VnaCB0byBzdXJ2aXZlIEhEQlNDQU4ncyBhc3N1bXB0aW9ucy4KCkEgcmVtaW5kZXIgdGhhdCB0aGUgbm9pc2UgYXJ0aWNsZXMgdGhhdCByZW1haW4sIGNvdWxkIGhhdmUgYmVlbiB1c2VmdWwgYXQgc29tZSBwb2ludCBpbiB0aW1lIChiYXNlZCB1cG9uIHRoZSBjbHVzdGVyUGF0aCBhbmFseXNpcyksIGJ1dCB3aXRoIGEgbWluUHRzVmFsdWUgb2YgMTUwIHdlcmUgY2xhc3NpZmllZCBhcyBub2lzZS4gVGhlcmUgYXJlIHNvbWUgaXNsYW5kcyB0aGVyZSB0aGF0IGFyZ3VlYWJseSBjb3VsZCBmb3JtIHVzZWZ1bCBjbHVzdGVycywgYnV0IEkgd2lsbCByZWx5IG9uIHRoZSBuZXh0IGFuYWx5c2lzIChiaWdyYW0gY2x1c3RlcmluZykgdG8gdGVhc2Ugb3V0IG91dCB0aGF0IHVzZWZ1bG5lc3MgcmF0aGVyIHRoYW4gcmVseSB1cG9uIHRoZSAicGFyYW1ldGVyIGNoYW5naW5nIiBhbmFseXNpcy4KCkZpbmFsbHksIEkgd2lsbCBicmluZyBiYWNrIHRoZXIgInZlcnkgbm9pc2V5IGRvY3VtZW50cyIsIHRob3NlIHRoYXQgYXJlIHByZXR0eSBtdWNoIGFsd2F5cyBjbGFzc2lmaWVkIGFzIG5vaXNlLCBpbnNwaXRlIG9mIHRoZSBtaW5QdHMgdmFsdWUgcHJvdmlkZWQgdG8gSERCU0NBTi4KCmBgYHtyIHZlcnlOb2lzZXlEb2N1bWVudEFuYWx5c2lzLCBtZXNzYWdlPUYsd2FybmluZz1GfQoKdHNuZUNvcmRGdWxsPC1mdWxsX2pvaW4odHNuZUNvcmQsdmVyeU5vaXNleURvY3VtZW50cykKCnRzbmVDb3JkRnVsbFtpcy5uYSh0c25lQ29yZEZ1bGwkdHNuZUNsdXN0ZXIpLF0kdHNuZUNsdXN0ZXI8LSAtMQp0c25lQ29yZEZ1bGxbaXMubmEodHNuZUNvcmRGdWxsJHRzbmVDbHVzdGVyTmFtZXMpLF0kdHNuZUNsdXN0ZXJOYW1lczwtICJ2ZXJ5Tm9pc2V5IgoKdHNuZUNvcmRGdWxsICU+JQogIG11dGF0ZShpc05vaXNlID0gaWZlbHNlKHRzbmVDbHVzdGVyTmFtZXMgJWluJSBjKCJOb2lzZSIsInZlcnlOb2lzZXkiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodHNuZUNsdXN0ZXJOYW1lcyA9PSAiTm9pc2UiLCJVbmNsdXN0ZXJlZCIsIk5ldmVyIENsdXN0ZXJlZCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgLCJDbHVzdGVyZWQiKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWNvbXAxLHk9Y29tcDIsZ3JvdXA9dHNuZUNsdXN0ZXJOYW1lcyxjb2w9aXNOb2lzZSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2wgPSBpc05vaXNlKSxhbHBoYT0wLjIpKwogIGxhYnMoeD0idHNuZUNvbXAxIiwKICAgICAgIHk9InRzbmVDb21wMiIsCiAgICAgICBsZWdlbmQ9IiIpKwogIHRoZW1lX2J3KCkrCiAgc3RhdF9lbGxpcHNlKGFlcyhhbHBoYT1pc05vaXNlKSxjb2xvdXI9ImJsYWNrIikrCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcz1jKDAuNiwwLDApKSsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCIjMWI5ZTc3IiwiI2Q5NWYwMiIsIiM3NTcwYjMiKSkrCiAgI3NjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIiNmYjgwNzIiLCIjYmViYWRhIiwiIzhkZDNjNyIpKSsKICBnZW9tX3RleHQoZGF0YT1jbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksZm9udGZhY2U9ImJvbGQiLGNvbD0iYmxhY2siLHNpemU9MykrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJiZWxvdyIpCmBgYAoKVGhlICJ2ZXJ5IG5vaXNleSBkb2N1bWVudHMiIiBvY2N1ciBldmVyeXdoZXJlIGFuZCBkb24ndCBhcHBlYXIgdG8gZm9ybSBkaXN0aW5jdCBjbHVzdGVycyBhcyB0aGUgIm5vaXNlIiBkb2N1bWVudHMgZG8uIFdoYXQgdGhpcyBzYXlzIHRvIG1lIGlzIHRoYXQgdGhlICJ2ZXJ5IG5vaXNleSBkb2N1bWVudHMiIGNvdWxkIHZlcnkgd2VsbCBjb250YWluIHNvbWUgdXNlZnVsIG9mIGludGVyZXN0aW5nIGluZm9ybWF0aW9uLCBidXQgdGhleSBhcmUgZGlmZmljdWx0IHRvIGNvYXggaW50byBhbnlmb3JtIG9mIHN0cnVjdHVyZSwgYWdhaW4gdGhpcyB3YXMgcHJldHR5IG11Y2ggc3VnZ2VzdGVkIGJ5IHRoZSBIREJTQ0FOIHJlc3VsdHMgdGhhdCBjb25zaXN0ZW50bHkgZmFpbGVkIHRvIHB1dCB0aGVzZSBkb2N1bWVudHMgaW50byBhbnkgb25lIGNsdXN0ZXIgcmVnYXJkbGVzcyBvZiB0aGUgcGFyYW1ldGVyIHZhbHVlcnMuIEkgd2lsbCBub3QgZGlzY2FyZCB0aGUgInZlcnkgbm9pc2V5IGRvY3VtZW50cyIgb3V0cmlnaHQsIGJ1dCBJIHdpbGwgY29udGludWUgdG8gZXhjbHVkZSB0aGVtIGZyb20gYW5hbHlzaXMgdGhhdCBlc3RhYmxpc2hlcyBjbHVzdGVycyBhbmQgdGhlIHByb3BlcnRpZXMgb2YgY2x1c3RlcnMuIFRoZSBkb2N1bWVudHMgdGhhdCBhcmUgc2ltcGx5IGxhYmVsbGVkICJOb2lzZSIgYXBwZWFyIHRvIGZvcm0gdGhlaXIgb3duIGNsdXN0ZXJzIGFuZCBhbHNvIGhhbmdvdXQgbmVhciBrbm93biBjbHVzdGVycywgYW5kIG1pZ2h0IGNvbnRhaW4gc29tZSBhZGRpdGlvbmFsIHVzZWZ1bCBzaWduYWwuCgpgYGB7ciB2ZXJ5Tm9pc2V5RG9jdW1lbnRBbmFseXNpc19QYXBlclZlcnNpb24sIGV2YWw9RkFMU0UsZWNobz1GQUxTRSxtZXNzYWdlPUYsd2FybmluZz1GfQp0c25lQ29yZEZ1bGwgJT4lCiAgbXV0YXRlKGlzTm9pc2UgPSBpZmVsc2UodHNuZUNsdXN0ZXJOYW1lcyAlaW4lIGMoIk5vaXNlIiwidmVyeU5vaXNleSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh0c25lQ2x1c3Rlck5hbWVzID09ICJOb2lzZSIsIlVuY2x1c3RlcmVkIiwiTmV2ZXIgQ2x1c3RlcmVkIikKICAgICAgICAgICAgICAgICAgICAgICAgICAsIkNsdXN0ZXJlZCIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzLGNvbD1pc05vaXNlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbCA9IGlzTm9pc2UpLGFscGhhPTAuMikrCiAgbGFicyh4PSJ0c25lQ29tcDEiLAogICAgICAgeT0idHNuZUNvbXAyIiwKICAgICAgIGxlZ2VuZD0iIikrCiAgdGhlbWVfYncoKSsKICBzdGF0X2VsbGlwc2UoYWVzKGFscGhhPWlzTm9pc2UpLGNvbG91cj0iYmxhY2siKSsKICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzPWMoMC40LDAsMCkpKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIiMxYjllNzciLCIjZDk1ZjAyIiwiIzc1NzBiMyIpKSsKICAjc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiI2ZiODA3MiIsIiNiZWJhZGEiLCIjOGRkM2M3IikpKwogICNnZW9tX3RleHQoZGF0YT1jbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksZm9udGZhY2U9ImJvbGQiLGNvbD0iYmxhY2siLHNpemU9MykrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICAjbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9IGVsZW1lbnRfYmxhbmsoKSxwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKGZpbGU9Ik5vaXNlRGlzdEluRG9jcy5wZGYiKQpgYGAKCiMjIyBEaWdnaW5nIGludG8gdGhlIG5vaXNlIAoKSSBkb24ndCB3YW50IHRvIGRpZyB0b28gZGVlcGx5IGludG8gdGhlIG5vaXNleSBkYXRhLCBidXQgSSB3aWxsIGxvb2sgdG8gc2VlIGlmIHRoZXJlIGFyZSBhbnkgb3RoZXIgdXNlZnVsICBvciBpbnRlcmVzdGluZyBjbHVzdGVycyB0aGF0IGZvcm0sIGFuZCBob3cgc2ltaWxhciBvciBkaWZmZXJlbnQgdGhleSBtYXkgYmUgZnJvbSB0aGUgbXVjaCBtb3JlIHByb25vdW5jZWQgY2x1c3RlcnMuIEV2ZW4gaWYgdGhlIHNtYWxsZXN0IGNsdXN0ZXIgc2l6ZSBpcyAiMiIsIHRoZXJlIGFyZSA1MzggZG9jdW1lbnRzIChhbmFseXNpcyBub3Qgc2hvdykgdGhhdCBkb24ndCByZWFsbHkgY2x1c3RlciB3aXRoIGFueXRoaW5nLCBzbyBub3QgYWxsIHRoZSBkb2N1bWVudHMgYXJlIHZlcnkgdXNlZnVsLiBUaGUgbWluaW11bSBjbHVzdGVyIHNpemUgdGhhdCBJIHdpbGwgdXNlIGhlcmUgaXMgMTAwLCB0aGlzIGlzIHN1YmplY3RpdmUgZGVjaXNpb24gYmVjYXVzZSB0aGVyZSBhcmUgZW5vdWdoIGFydGljbGVzIGhlcmUgdG8gcmVhbGx5IG1ha2UgYSBjb21wZWxpbmcgY2FzZSAodG8gbWUpIHRoYXQgdGhlIHRvcGljIG9mIHRoZSBjbHVzdGVyIGlzIGludGVyZXN0aW5nIGFuZCB3b3J0aC13aGlsZSB0byBiZSBpdCdzIG93biBlbnRpdHkuIE90aGVycyBkb2luZyBhIG1vcmUgbnVhbmNlZCBhbmFseXNpcyBtYXkgZGlzYWdyZWUgd2l0aCB0aGlzIGNob2ljZS4gCgoKYGBge3IgY2x1c3Rlck5vaXNlLCBtZXNzYWdlPUYsd2FybmluZz1GfQpub2lzZU9ubHk8LSBmaWx0ZXIodHNuZUNvcmQsdHNuZUNsdXN0ZXJOYW1lcyA9PSAiTm9pc2UiKQoKY2wgPC0gaGRic2Nhbihub2lzZU9ubHlbLDI6M10sIG1pblB0cyA9IDUwKQpub2lzZU9ubHkkdHNuZUNsdXN0ZXI8LShjbCRjbHVzdGVyICsgMjAwKQoKbm9pc2VPbmx5PC1ub2lzZU9ubHkgJT4lCiAgZ3JvdXBfYnkodHNuZUNsdXN0ZXIpICU+JQogIG11dGF0ZSh0c25lQ2x1c3Rlck5hbWVzID0gZ2V0VG9wVGVybXMoUE1JRCxjbHVzdFZhbHVlPXRzbmVDbHVzdGVyLHRvcE5WYWwgPSAzKSkKCm5vaXNlQ2x1c3Rlck5hbWVzIDwtIG5vaXNlT25seSU+JQogIGdyb3VwX2J5KHRzbmVDbHVzdGVyTmFtZXMpICU+JQogIHN1bW1hcmlzZShtZWRYID0gbWVkaWFuKGNvbXAxKSwKICAgICAgICAgICAgbWVkWSA9IG1lZGlhbihjb21wMikpICU+JQogIGZpbHRlcih0c25lQ2x1c3Rlck5hbWVzICE9ICJOb2lzZSIpICU+JQogICBmaWx0ZXIodHNuZUNsdXN0ZXJOYW1lcyAhPSAiaXNvbC1zdHJhaW4tZ2Vub20iKQogIAoKI25vdGUgLSBpc29sLXN0cmFpbi1nZW5vbSBpcyBhIHdlaXJkIGFuZCBleHBhbnNpemUgY2x1c3RlciwgdGhhdCBpcyB3b3J0aCByZW1vdmluZy4Kbm9pc2VPbmx5ICU+JQogIGZpbHRlcighKHRzbmVDbHVzdGVyTmFtZXMgJWluJSBjKCJOb2lzZSIsImlzb2wtc3RyYWluLWdlbm9tIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixjb2xvdXI9dHNuZUNsdXN0ZXJOYW1lcyxncm91cD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4yKSsKICB0aGVtZV9idygpKwogIHN0YXRfZWxsaXBzZShjb2w9ImJsYWNrIikrCiAgZ2VvbV90ZXh0KGRhdGE9bm9pc2VDbHVzdGVyTmFtZXMsYWVzKHg9bWVkWCx5PW1lZFksbGFiZWw9dHNuZUNsdXN0ZXJOYW1lcyksY29sPSJibGFjayIsc2l6ZT0zKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMobWluKHRzbmVDb3JkJGNvbXAyKSxtYXgodHNuZUNvcmQkY29tcDIpKSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cz1jKG1pbih0c25lQ29yZCRjb21wMSksbWF4KHRzbmVDb3JkJGNvbXAxKSkpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iTm9uZSIpCmBgYAoKRmluYWxseSwgSSdsbCBzaG93IHRoaXMgd2l0aCB0aGUgb3RoZXIga25vd24gY2x1c3RlcnMgdG8gc2VlIGhvdyBtdWNoIG1vcmUgdXNlZnVsIGluZm9ybWF0aW9uIGNsdXN0ZXJpbmcgdGhlIG5vaXNlIHByb3ZpZGVzIHRvIHRoZSBtdWNoIG1vcmUgZXN0YWJsaXNoZWQgY2x1c3RlcnMuCgpgYGB7ciBub2lzZVdpdGhDbHVzdGVycywgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KCmlkeE5vaXNlPC1tYXRjaChub2lzZU9ubHkkUE1JRCx0c25lQ29yZCRQTUlEKQoKdG1wPC10c25lQ29yZCR0c25lQ2x1c3Rlck5hbWVzCnRtcFtpZHhOb2lzZV08LW5vaXNlT25seSR0c25lQ2x1c3Rlck5hbWVzCgp0bXAyPC10c25lQ29yZAp0bXAyJHRzbmVDbHVzdGVyTmFtZXMyPC10bXAKCgojbm90ZSwgdXNlZnVsIHRvIHZpZXcgdGhpcyBvbiBhIG11Y2ggbGFyZ2VyIHNjcmVlbgp0bXAyICU+JQogIG11dGF0ZShpc05vaXNlID0gaWZlbHNlKHRzbmVDbHVzdGVyTmFtZXMgPT0gIk5vaXNlIiwiTm9pc2UiLCJDbHVzdGVyIikpICU+JQogIGZpbHRlcighKHRzbmVDbHVzdGVyTmFtZXMyICVpbiUgYygiTm9pc2UiLCJpc29sLXN0cmFpbi1nZW5vbSIpKSklPiUgCiAgZ2dwbG90KGFlcyh4PWNvbXAxLHk9Y29tcDIsZ3JvdXA9dHNuZUNsdXN0ZXJOYW1lczIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sID0gaXNOb2lzZSksYWxwaGE9MC4yKSsKICB0aGVtZV9idygpKwogICNmYWNldF93cmFwKH5pc05vaXNlKSsKICBzdGF0X2VsbGlwc2UoYWVzKGxpbmV0eXBlPWlzTm9pc2UpKSsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCIjMWI5ZTc3IiwiIzc1NzBiMyIpKSMrCiAgI2dlb21fdGV4dChkYXRhPW5vaXNlQ2x1c3Rlck5hbWVzLGFlcyh4PW1lZFgseT1tZWRZLGxhYmVsPXRzbmVDbHVzdGVyTmFtZXMpLGNvbD0iYmxhY2siLHNpemU9MykrCiAgI2dlb21fdGV4dChkYXRhPWNsdXN0ZXJOYW1lcyxhZXMoeD1tZWRYLHk9bWVkWSxsYWJlbD10c25lQ2x1c3Rlck5hbWVzKSxjb2w9InJlZCIsc2l6ZT0zKQoKYGBgClN1YmplY3RpdmVseSwgc29tZSBvZiB0aGVzZSBhZGRpdGlvbmFsIGNsdXN0ZXJzIGFyZSBpbnRlcmVzdGluZywgYW5kIG90aGVycyBhcmUgbm90IGFzIG11Y2guIEluIHRoZSBzdWJzZXF1ZW50IGJpZ3JhbSBhbmFseXNpcyB0YWtlIGEgbG9vayBhdCAicGF0aG9nZW5zIiBhbmQgInRvcGljcyIuIEZvciBteSBhbmFseXNpcyBJIGFtIG1vc3RseSBpbnRlcmVzdGluZyBpbiB0b3BpY3MsIGJ1dCB3aGF0IGlzIGNsZWFyIGlzIHRoYXQgdGhlcmUgaXMgYW4gb3ZlcndoZWxtaW5nIHNpZ25hbCBmcm9tIHBhdGhvZ2VucyBpbiB0aGVzZSBkYXRhIHRoYXQgbWFrZSBpcyBoYXJkIHRvIGFuYWx5emUgdG9waWNzIGFjcm9zcyBwYXRob2dlbnMuIFdoYXQgaXMgYWxzbyBjbGVhciBpcyB0aGF0IHNvbWUgb2YgdGhvc2UgaW50ZXJlc3RpbmcgdG9waWNzIChsaWtlIG91dGJyZWFrcyksIGZvcm0gdGhlaXIgb3duIGNsdXN0ZXJzIGluc3RlYWQgb2YgYmVpbmcgZW1iZWRkZWQgd2l0aGluIHBhdGhvZ2VuIGNsdXN0ZXJzLiBJJ2xsIHNob3cgaW4gYSBtb21lbnQsIHRoYXQgb3V0YnJlYWsgY2FuIGJlIGZvdW5kIHdpdGhpbiBwYXRob2dlbiBjbHVzdGVycyB0b28sIGJ1dCBJIGFtIG5vdCBjZXJ0YWluIHdoeSAib3V0YnJlYWtzIiBpbiBwYXJ0aWN1bGFyIGZvcm1zIGl0cyBvd24gY2x1c3Rlci4KCk5vdyBJIHdpbGwgYXR0ZW1wdCB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBjbHVzdGVycyB1c2luZyB0aGUgc2FtZSBwYXJhbWV0ZXJzIHVzaW5nIHRoZSB2ZXJ5IG5vaXNleSBjbHVzdGVycy4gVXNpbmcgdGhlIHNtYWUgcGFyYW1ldGVyLCBvbmx5IHRocmVlIGNsdXN0ZXJzIGFyZSBmb3JtZWQKCmBgYHtyIHZlcnlOb2lzZUNsdXN0ZXJzLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KCmNsIDwtIGhkYnNjYW4odmVyeU5vaXNleURvY3VtZW50c1ssMzo0XSwgbWluUHRzID0gNTApCgp2ZXJ5Tm9pc2V5RG9jdW1lbnRzJHRzbmVDbHVzdGVyPC0oY2wkY2x1c3RlciArIDMwMCkKCnZlcnlOb2lzZXlEb2N1bWVudHM8LXZlcnlOb2lzZXlEb2N1bWVudHMgJT4lCiAgZ3JvdXBfYnkodHNuZUNsdXN0ZXIpICU+JQogIG11dGF0ZSh0c25lQ2x1c3Rlck5hbWVzID0gZ2V0VG9wVGVybXMoUE1JRCxjbHVzdFZhbHVlPXRzbmVDbHVzdGVyLHRvcE5WYWwgPSAzKSkKCnZOb2lzZUNsdXN0ZXJOYW1lcyA8LSB2ZXJ5Tm9pc2V5RG9jdW1lbnRzJT4lCiAgZ3JvdXBfYnkodHNuZUNsdXN0ZXJOYW1lcykgJT4lCiAgc3VtbWFyaXNlKG1lZFggPSBtZWRpYW4oY29tcDEpLAogICAgICAgICAgICBtZWRZID0gbWVkaWFuKGNvbXAyKSkgJT4lCiAgZmlsdGVyKHRzbmVDbHVzdGVyTmFtZXMgIT0iTm9pc2UiKQogIAojbm90ZSAtIGlzb2wtc3RyYWluLWdlbm9tIGlzIGEgd2VpcmQgYW5kIGV4cGFuc2l6ZSBjbHVzdGVyLCB0aGF0IGlzIHdvcnRoIHJlbW92aW5nLgp2ZXJ5Tm9pc2V5RG9jdW1lbnRzICU+JQogIGdncGxvdChhZXMoeD1jb21wMSx5PWNvbXAyLGNvbD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4yKSsKICB0aGVtZV9idygpKwogICNzdGF0X2VsbGlwc2UoYWVzKGdyb3VwPXRzbmVDbHVzdGVyTmFtZXMpLGNvbD0iYmxhY2siKSsKICAjZ2VvbV90ZXh0KGRhdGE9dk5vaXNlQ2x1c3Rlck5hbWVzLGFlcyh4PW1lZFgseT1tZWRZLGxhYmVsPXRzbmVDbHVzdGVyTmFtZXMpLGNvbD0iYmxhY2siLHNpemU9MykrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKG1pbih0c25lQ29yZCRjb21wMiksbWF4KHRzbmVDb3JkJGNvbXAyKSkpKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHM9YyhtaW4odHNuZUNvcmQkY29tcDEpLG1heCh0c25lQ29yZCRjb21wMSkpKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Ik5vbmUiKQoKYGBgCkFnYWluLCB0aGVzZSBkb2N1bWVudHMgZG9uJ3QgaGF2ZSBzbWFsbGVyIGdyb3VwcyB0aGF0IGFyZSBwYXJ0aWN1bGFyaWx5IGludGVyc3RpbmcuICAKCgojIyBCaWdyYW0gY2x1c3RlcmluZyAKCkkgd2lsbCBub3cgcmVjcnVpdCB0aGUgYmlncmFtIGRhdGFzZXQgdG8gaGVscCBtZSBpZGVudGlmeSBjcm9zcy1jdXR0aW5nIHRvcGljcyBiZXR3ZWVuIHRoZXNlIGRhdGFzZXRzLkF0IGEgaGlnaCBsZXZlbCwgYW5kIHVzaW5nIG9ubHkgc2luZ2xlIHRlcm1zLCB0aGUgZGF0YSBjbHVzdGVyZWQgcHJpbWFyaWx5IGJ5IGRpc2Vhc2UuIEJ1dCwgSSBrbm93IHRoZXJlIGFyZSBjcm9zcyBjdXR0aW5nIHRvcGljcyBiZXR3ZWVuIGRpc2Vhc2VzIHRoYXQgc2hvdWxkIGV4aXN0LCBmb3IgZXhhbXBsZSwgZHJ1ZyByZXNpc3RhbmNlLCBvciBvdXRicmVha3MuIFRob3NlIGNyb3NzLWN1dHRpbmcgdG9waWNzIGFyZSBsaWtlbHkgYnVycmllZCBiZW5lYXRoIHRoZSBkaXNlYXNlIHNpZ25hbCwgc28gSSB3aWxsIG5vdyB0cnkgdG8gcHVsbCB0aGVtIG91dCB1c2luZyB0aGUgYmlncmFtIGFuYWx5c2lzLiBJIGFtIGdvaW5nIHRvIHVzZSB0aGUgY2x1c3RlcnMgZnJvbSB0aGUgc2luZ2xlIHRlcm0gYW5hbHlzaXMgYW5kIHRoZWlyIGNvLW9yZGluYXRlcyB0byBjb25kdWN0IHRoZSBiaWdyYW0gYW5hbHlzaXMuCgpUaGUgYmlncmFtIGFuYWx5c2lzIGlzIHBhcnRpY3VsYXJpbHkgdXNlZnVsIHNvIHRoYXQgSSBjYW4gZmlndXJlIG91dCB3aGF0J3MgZ29pbmcgb24gYW5kIGhvdyBiaWdyYW1zIG1heSBiZSByZWxhdGVkLiBUaGlzIGFuYWx5c2lzIGNvdWxkIGJlIGRvbmUgd2l0aCB0aGUgc2luZ2xlIHRlcm0gYW5hbHlzaXMsIGJ1dCBJIGZvdW5kIHRoYXQgdG8gYmUgbWVzc2llciBhbmQgbW9yZSBkaWZmaWN1bHQgdG8gdW5kZXJzdGFuZCB0aGUgcmVzdWx0cywgd2hlcmVhcyB1c2luZyBiaWdyYW1zIHdhcyBtdWNoIG1vcmUgdXNlZnVsLgoKYGBge3IgYmlncmFtQ2x1c3RlcmluZywgbWVzc2FnZT1ULHdhcm5pbmc9VH0KYmlncmFtX2RmPC1yZWFkUkRTKGZpbGU9ImRhdGEvZ2VuRXBpRGF0YV9pbml0aWFsUXVlcnkyX0JJR1JBTV90aWR5RGF0YS5SRFMiKQoKc3RvcmVkZjwtYmlncmFtX2RmCmJpZ3JhbV9kZjwtbGVmdF9qb2luKHRzbmVDb3JkLGJpZ3JhbV9kZikKCiNyZW1vdmUgYW55IGJpZ3JhbXMgdGhhdCBvY2N1ciBpbiBmZXdlciB0aGFuIDEwIGFydGljbGVzIGluIGEgY2x1c3RlcgpyZW1vdmVCaWdyYW0gPC0gYmlncmFtX2RmICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShiaWdyYW0sdHNuZUNsdXN0ZXJOYW1lcykgJT4lCiAgY291bnQoKSAlPiUKICBmaWx0ZXIobm48IDEwKQoKYmlncmFtX2RmIDwtIGJpZ3JhbV9kZiAlPiUgYW50aV9qb2luKHJlbW92ZUJpZ3JhbSkKCiNjb3VudCBob3cgb2Z0ZW4gYSBiaWdyYW0gb2NjdXJzIHdpdGhpbiBlYWNoIGdyb3VwIChlYWNoIHBhcGVyIG9ubHkgZ2l2ZXMgMSkKYmlncmFtRGlzdDwtYygpCiAgCmZvcihjbHVzdGVyTmFtZSBpbiB1bmlxdWUoYmlncmFtX2RmJHRzbmVDbHVzdGVyTmFtZXMpKXsKICBudW1QYXBlcnM8LWJpZ3JhbV9kZiAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIGZpbHRlcih0c25lQ2x1c3Rlck5hbWVzID09IGNsdXN0ZXJOYW1lKSAlPiUKICAgIHNlbGVjdChQTUlEKSU+JQogICAgdW5pcXVlKCkgJT4lCiAgICBjb3VudCgpCiAgICAKICB0bXA8LWJpZ3JhbV9kZiAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIGZpbHRlcih0c25lQ2x1c3Rlck5hbWVzID09IGNsdXN0ZXJOYW1lKSAlPiUKICAgIHNlbGVjdChiaWdyYW0sUE1JRCkgJT4lCiAgICBkaXN0aW5jdCgpICU+JQogICAgZ3JvdXBfYnkoYmlncmFtKSU+JQogICAgY291bnQoKSAlPiUKICAgIG11dGF0ZShmcmVxID0gbi9udW1QYXBlcnMkbikKICAKICB0bXAkdHNuZUNsdXN0ZXJOYW1lczwtcmVwKGNsdXN0ZXJOYW1lLG5yb3codG1wKSkKICBiaWdyYW1EaXN0PC1yYmluZChiaWdyYW1EaXN0LHRtcCkKfQoKYmlncmFtRGlzdEZpbHQ8LWJpZ3JhbURpc3QgJT4lCiAgZmlsdGVyKGZyZXEgPiAuMSkgCgoKI25jbHVzdApuQ2x1c3Q8LWJpZ3JhbV9kZiR0c25lQ2x1c3Rlck5hbWVzICU+JSB1bmlxdWUoKSAlPiUgbGVuZ3RoKCkKCiNmaW5hbGx5LCB0YWJ1bGF0ZSB3aGljaCBiaWdyYW0gb2NjdXIgdGhlIG1vc3QgZnJlcXVlbnRseSBhY3Jvc3MgY2x1c3RlcnMKdG1wPC1iaWdyYW1EaXN0RmlsdCAlPiUKICBzZWxlY3QoYmlncmFtLHRzbmVDbHVzdGVyTmFtZXMpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICBjb3VudCgpICU+JQogIG11dGF0ZShmcmVxID0gbiAvIG5DbHVzdCkKCndyaXRlLmNzdih0bXAsZmlsZT0iZGF0YS9iaWdyYW1Dcm9zc0NsdXN0ZXJfMi5jc3YiKQoKYGBgCgpJIGhhdmUgbWFudWFsbHkgYW5ub3RhdGVkIHRob3NlIHRlcm1zIHRvIGNsdXN0ZXIgdGhlbSBpbnRvIGhpZ2hlciBsZXZlbCBjYXRlZ29yaWVzLiBJIHdpbGwgbm93IGV4cGxvcmUgdGhlIGJpZ3JhbSB0ZXJtcywgaG93IEkndmUgY2x1c3RlcmVkIG1hbnVhbGx5IGFubm90YXRlZCB0aGVtLCBhbmQgd2hhdCBJIHdhcyBleHBlY3RpbmcgdG8gc2VlLiBDdXJyZW50bHksIGVhY2ggYmlncmFtIGhhcyBvbmx5ICpPTkUqIGFubm90YXRpb24uCgpgYGB7ciBzaG93TWFudWFsQmlncmFtQ2x1c3RlciwgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KCm1hbkFubm90PC1yZWFkLmNzdihmaWxlPSJkYXRhL2JpZ3JhbUNyb3NzQ2x1c3Rlcl8yX21hbnVhbEFubm90LmNzdiIsaGVhZGVyPVQpCm1hbkFubm90JEFubm90YXRlPC10cmltd3MobWFuQW5ub3QkQW5ub3RhdGUpCgojZmlyc3QsIGp1c3Qgc2hvdyBob3cgbWFueSBiaWdyYW1zIEkndmUgYXNzaWduZWQgdG8gc29tZSBhbm5vdGF0aW9uCm1hbkFubm90ICU+JQogIGdyb3VwX2J5KEFubm90YXRlKSAlPiUKICBjb3VudCgpICU+JQogIGdncGxvdChhZXMoeT1ubix4PXJlb3JkZXIoQW5ub3RhdGUsLW5uKSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKwogIHRoZW1lX2J3KCkrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1ubiksbnVkZ2VfeT0zLGNvbD0icmVkIikrCiAgeGxhYigiTWFudWFsbHkgYW5ub3RhdGUgY2F0ZWdvcmllcyIpKwogIHlsYWIoIlRvdGFsIG51bWJlciBvZiBiaWdyYW1zIikrCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLHZqdXN0PTAuNSxoanVzdD0xKSkKYGBgClRoZSBzcGVjaWZpYyBkZWNpc2lvbnMgYmVoaW5kIGVhY2ggb2YgdGhlIG1hbnVhbGx5IGFubm90YXRpb25zIGFyZSBhbHNvIG5vdGVkIGluIHRoZSBDU1YgZmlsZSBmb3IgdGhlIG1hbnVhbCBhbm5vdGF0aW9ucy4gSSBuZWVkIHRvIG5vdyBtYWtlIHNvbWUgZGVjaXNpb25zIGFib3V0IHdoYXQgSSBrZWVwLCB3aGF0IEkgbmVzdCB1bmRlciBzb21lIGxldmVsIChpZiBhdCBhbGwpLCBhbmQgd2hldGhlciBJIHNob3VsZCBnbyBiYWNrIGFuZCBjb25zaWRlciBhc3NpZ25tZW50IGJpZ3JhbSB0byBtdWx0aXBsZSB0YWdzLiAKClRvIGJlZ2luLCB0aGUgbW9zdCBjb21tb24gdGFnIGlzIHRoZSAibm90LXVzZWZ1bCIgdGFnLiBJJ3ZlIG9mdGVuIGNsYXNzaWZpZWQgYSB0ZXJtIGFzICJub3QtdXNlZnVsIiAoZm9yIHRoZSBwdXJwb3NlcyBvZiBteSBhbmFseXNpcykgaWYgaXQgaXMgdG9vIGdlbmVyYWwsICgiZ2Vub21fc2VxdWVuYyIpLCBhIG1ldGhvZCAoImNoYWluX3JlYWN0aW9uIiwgImNvbmZpZGVuX2ludHJ2IiwiZWxlY3Ryb3Bob3Jlc2lfcGZnZSIpLCBvciBpZiBpdCB3YXMganVzdCBlbmdsaXNoIHdyaXRpbmcgKCJhbmFseXNpX3JldmVhbCIpLiBJIGRvIG5vdCBpbmNsdWRlIHRoZSAibm90LXVzZWZ1bCIgYmlncmFtcyBpbiBmdXJ0aGVyIGFuYWx5c2lzLgoKVGhlIG5leHQgbW9zdCBjb21tb24gY2F0ZWdvcnkgaXMgInBhdGhvZ2VuIiwgdGhlc2UgYXJlIGNvbW1vbmx5IG9jY3VyaW5nIGJpZ3JhbXMgdGhhdCBiYXNpY2FsbHkgZGVzY3JpYmUgYSBidWcuIEZvciBleGFtcGxlICJkZW5ndV92aXIiLCB3aGljaCBtYXRjaGVzIHRoZSBjbHVzdGVyIG5hbWVzIGZyb20gdGhlIHNpbmdsZSB0ZXJtIGFuYWx5c2lzLiBJIGNvbnNpZGVyIHRoZSBiaWdyYW1zIHBhdGhvZ2VuX3gsIG9yIHhfcGF0aG9nZW4sICB0byBiZWxvbmcgdG8gdGhlIHBhdGhvZ2VuIGFubm90YXRpb24gaWYgeCA9ICJ2aXIiLCJpc29sIiwidmlydXMiIG9yIGlmIHggaXMgdGhlIHNwZWNpZXMgbmFtZSAoaS5lICJzdGFwaCBhdXJldXMiKS4gSWYgeCA9ICJzdHJhaW4iLCB0aGUgYW5ub3RhdGlvbiBpcyBjdXJyZW50bHkgImNoYXJhY3RlcmlzYXRpb24iLCBob3dldmVyIHRoaXMgbWlnaHQgYWxzbyBiZSBhIGdvb2QgY2FzZSBmb3IgYWxsb3dpbmcgYSBiaWdyYW0gdG8gYmVsb25nIHRvIG11bHRpcGxlIGFubm90YXRpb25zLiBTb21lIHBhdGhvZ2VucyBmb3VuZCBpbiB0aGUgYmlncmFtIGFuYWx5c2lzIHdoZXJlIG5vdCBwaWNrZWQgdXAgYnkgdGhlIHNpbmdsZSB0ZXJtIGNsdXN0ZXJpbmdzLCBmb3IgZXhhbXBsZSwgdGhlcmUgaXMgbm8gImtsZWJzaWVsbGFfcG5ldW1vbmlhIiBjbHVzdGVyLCBidXQgdGhlcmUgaXMgYW4gImluZmx1ZW56YSIgY2x1c3Rlci4gSW4gYSBtb21lbnQgSSdsbCBzaG93IGhvdyBtYW55ICJuZXciIHBhdGhvZ2VucyBhcmUgZm91bmQgYnkgdGhlIGJpZ3JhbSBhbmFseXNpcywgYW5kIHRvIHdoaWNoIGNsdXN0ZXJzIHRoZXkgYmVsb25nLiBCdXQgbGFzdGx5LCBhbmQgbW9zdCBpbXBvcnRhbnRseSwgdGhlICJwYXRob2dlbiIiIGFubm90YXRpb24gaXNuJ3QgcmVhbGx5IGEgY3Jvc3MtY3V0dGluZyB0b3BpYywgaXQganVzdCBoYXBwZW5zIHRvIHNob3cgdXAuIEknZCBsaWtlIHRvIG1ha2Ugc3VyZSB0aGF0IEkgc3R1ZHkgdGhlIHZpc3VhbGl6YXRpb24gYWNyb3NzIHBhdGhvZ2Vucywgd2hpY2ggaW5jbHVkZSBjYXB0dXJpbmcgc29tZSBvZiB0aGUgcGF0aG9nZW5zIHRoYXQgZG9uJ3QgZm9ybSB0aGVpciBvd24gY2x1c3RlcnMuCgpgYGB7ciBwYXRob2dlbkFubm90YXRpb24sIG1lc3NhZ2U9Rix3YXJuaW5nPUZ9CgpwYXRoQW5ub3Q8LW1hbkFubm90ICU+JQogIGZpbHRlcihBbm5vdGF0ZSA9PSAicGF0aG9nZW4iKSAlPiUKICBzZWxlY3QoYmlncmFtLEFubm90YXRlKQoKcGF0aENsdXN0ZXI8LWJpZ3JhbV9kZiAlPiUKICBpbm5lcl9qb2luKHBhdGhBbm5vdCkKCnNvcnRPcmRlcjwtcGF0aENsdXN0ZXIgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChiaWdyYW0sdHNuZUNsdXN0ZXJOYW1lcykgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBncm91cF9ieShiaWdyYW0pJT4lCiAgY291bnQoKSAlPiUKICBhcnJhbmdlKC1uKQoKcGF0aENsdXN0ZXJDb3VudDwtIHBhdGhDbHVzdGVyICU+JQogIHVuZ3JvdXAoKSU+JQogIHNlbGVjdChiaWdyYW0sdHNuZUNsdXN0ZXJOYW1lcykgJT4lCiAgZ3JvdXBfYnkoYmlncmFtLHRzbmVDbHVzdGVyTmFtZXMpJT4lCiAgY291bnQoKQoKcGF0aENsdXN0ZXJDb3VudCRiaWdyYW0gPC0gZmFjdG9yKHBhdGhDbHVzdGVyQ291bnQkYmlncmFtLGxldmVscz1zb3J0T3JkZXIkYmlncmFtKQoKZ2dwbG90KHBhdGhDbHVzdGVyQ291bnQsIGFlcyh4PXRzbmVDbHVzdGVyTmFtZXMseT1iaWdyYW0pKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPW4pLGNvbG91cj0id2hpdGUiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLGhqdXN0PTEsdmp1c3Q9MC41KSkKYGBgCkl0J3MgY2xlYXIgdGhhdCBhIGxvdCBvZiBpbnRlcmVzdGluZyBzdHVmZiBpcyBpbiB0aGUgIk5vaXNlIiBjYXRlZ29yeSwgd2hlcmUgc29tZSBwYXRob2dlbiByZWFsbHkganVzdCBkaWRuJ3QgZ2V0IGNsdXN0ZXJlZC4gSXQncyBwb3NzaWJsZSB0byBzaG93IHdoZXJlIGV2ZXJ5dGhpbmcgZW5kcyB1cCwgZWl0aGVyIGp1c3Qgb24gdGhlIG91dHNpZGUgb2YgZXhpc3RpbmcgY2x1c3RlcnMgb3Igd2l0aGluIHRoZW0uIEl0IGlzIGFsc28gZXZpZGVuY2UgdGhhdCBwYXRob2dlbnMgY3Jvc3MtY3V0IGEgbnVtYmVyIG9mIGdyb3VwcyBpbiB0aGUgbWlkZGxlIHRoYXQgaGF2ZSBub3RoaW5nIHJlYWxseSB0byBkbyB3aXRoIHBhdGhvZ2VucywgYnV0IGFyZSBtb3JlIGFib3V0IHRvcGljcy4gRm9yIGV4YW1wbGUsIHRoZSBFLmNvbGkgcGF0aG9nZW4gYmlncmFtIGlzIHByZWRvbWluYXRlbHkgZnJvdW5kIGluIHRoZSAiY29saS1zdHJhaW4iIGNsdXN0ZXIsIGJ1dCBvY2NydXMgYWNyb3NzIGEgbnVtYmVyIG9mIHB0aGVyIGNsc3V0ZXJzIChtbHZhLWlzb2wsIHJlc2lzdC1pc29sKSBlY3QuIAoKYGBge3IgcGF0aG9nZW5Bbm5vdGF0aW9uMiwgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KYmlncmFtTmFtZXMgPC0gcGF0aENsdXN0ZXIgJT4lCiAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICBzdW1tYXJpc2UobWVkWCA9IG1lZGlhbihjb21wMSksCiAgICAgICAgICAgIG1lZFkgPSBtZWRpYW4oY29tcDIpKQoKdHNuZUNvcmQgJT4lCiAgdW5ncm91cCgpJT4lCiAgbXV0YXRlKGlzUGF0aEJpZ3JhbSA9IGlmZWxzZShQTUlEICVpbiUgdW5pcXVlKHBhdGhDbHVzdGVyJFBNSUQpLCJwYXRob2dlbiIsIm5vdC1wYXRob2dlbiIpKSAlPiUKICBtdXRhdGUoaXNOb2lzZSA9IGlmZWxzZSh0c25lQ2x1c3Rlck5hbWVzID09ICJOb2lzZSIsIk5vaXNlIiwiQ2x1c3RlciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbCA9IGlzUGF0aEJpZ3JhbSxhbHBoYT1pc1BhdGhCaWdyYW0pKSsKICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzPWMoMC4xLDAuNykpKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoImdyZXkiLCJibHVlIikpKwogIHN0YXRfZWxsaXBzZShhZXMobGluZXR5cGU9aXNOb2lzZSksY29sPSJkYXJrZ3JleSIpKwogICNnZW9tX3RleHRfcmVwZWwoZGF0YT1iaWdyYW1OYW1lcyxhZXMoeD1tZWRYLHk9bWVkWSxsYWJlbD1iaWdyYW0pLGNvbD0iYmxhY2siLHNpemU9MykrCiAgdGhlbWVfYncoKQpgYGAKClNvIHdoYXQgaXMgY2xlYXIgaXMgdGhhdCBjbHVzdGVycyBhcm91bmQgdGhlIG91dHNpZGUgKGF3YXkgZnJvbSB0aGUgbWlkZGxlKSwgYXJlIGFsbCBkaXNlYXNlIHRvcGljcywgd2hlcmVhcyB0aGUgc3R1ZmYgaW4gdGhlIG1pZGRsZSBkb2Vzbid0IHJlYWxseSBoYXZlIGFueXRoaW5nIHRvIGRvIHdpdGggcGF0aG9nZW5zLCBJIGFtIG5vdCB5ZXQgc3VyZSB3aGF0J3MgaW4gdGhvc2UgY2x1c3RlcnMgaW4gdGhlIGNlbnRlciwgYnV0IEknbGwgZmlndXJlIGl0IG91dCBhcyBJIG1vdmUgdGhyb3VnaCB0aGUgYW5ub3RhdGlvbnMuIEl0IHNlZW1zIHRoYXQgbWFueSBhcnRpY2xlcyBjbGFzc2lmaWVkIGFzIG5vaXNlIG1pZ2h0IGp1c3QgYmUgb24gdGhlIG91dCByZWFjaGVzIG9mIGtub3duIGNsdXN0ZXJzLiBJdCBzZWVtcyB0aGF0IHNvbWUgY2x1c3RlcnMgY291bGQgZXZlbiBiZSBicm9rZW4gdXAgc29tZSBtb3JlLiBGb3IgZXhhbXBsZSBpc29sX3Jlc2lzdCBzZWVtcyB0byBhY3R1YWxseSBjb250YWluIGtsZWJzaWxsYSBhbmQgcHN1ZWRlbW9uYXMgZGF0YS4gRS4gY29saSBzZWVtIHRvIGJlIGFsbCBvdmVyIHRoZSBwbGFjZSwgYnV0IGxhcmdlbHkgcmVsZWdhdGVkIHRvIG5vaXNlIGFuZCBzYW1lIHdpdGggaGVsaW9iYWN0ZXJfcHlsb3JpICwgdGhvc2UgbWF5IHJlcXVpcmUgYSBsaXR0bGUgYml0IG1vcmUgdHdlYWtpbmcuCgpPbmUgb3RoZXIgbmVhdCB0aGluZywgaXQgc2VlbXMgdGhhdCB0aGUgZGF0YSBhbHNvIHNlcGVyYXRlIGludG8gYmFjdGVyaWEgKHRvd2FyZCB0aGUgcmlnaHQgb2YgdGhlIGZpZ3VyZSkgYW5kIHZpcnVzZXMgKHRvd2FyZCB0aGUgbGVmdCBvZiB0aGUgZmlndXJlKS4KCk9uIGEgZmluYWwgbm90ZSwgdGhlcmUgYXJlIGNlcnRhaW5seSBzb21lIGJ1Z3MgaGVyZSB0aGF0IGFyZW4ndCByZXByZXNlbnRlZCBhbmQgSSdkIGxpa2UgdG8ga25vdyBhIGxpdHRsZSBiaXQgbW9yZSBvZiB3aHkuIFdoZXJlIGlzIG1lYXNsZXMgZm9yIGV4YW1wbGUsIG9yIHdoZXJlIGlzIGVib2xhPyBXaGVyZSBhcmUgYW55IG9mIHRoZSBzZXh1YWxseSB0cmFuc21pdHRlZCBkaXNlYXNlcy4gV2hhdCdzIHBvc3NpYmxlIGlzIHRoYXQgdGhlIGFydGljbGVzIHRoYXQgZG9taW5hdGUgZ2Vub21pYyBlcGlkZW1pb2xvZ3kgZm9yIGluZmVjdGlvdXMgZGlzZWFzZXMgYXJlIHByZWRvbWluYXRlbHkgYWJvdXQgdGhlIG1ham9yIHBhdGhvZ2VucyB0aGF0IGFyZSBlYXNpbHkgdmlzaWJsZSBpbiB0aGUgY2x1c3RlcmluZyBhbmFseXNpcy4gQnV0IG1lYXNsZXMgcGFwZXJzIGFyZSBtb3JlIG9mIGEgInB1YmxpYyBoZWFsdGgiIGFuZCBsZXNzIG9mICJnZW5vbWljcyIgYXJlYS4gU2luY2UgSSB3YW50IHRvIHJldHJpZXZlIGdlbm9taWNzIGFydGljbGVzLCB0aGlzIGNvdWxkIGJlIGZpbmUsIGJ1dCBJIHdhbnQgdG8gc2VlIGlmIHRoYXQgaXMgYWN0dWFsbHkgdGhlIGNhc2UuIFRvIGRvIHNvLCBJJ2xsIGV4cGxvcmUgd2hlcmUgY2VydGFpbiBwYXRob2dlbnMgSSBleHBlY3QgdG8gZmluZCBleGlzdCwgaW4gdGhpcyBkYXRhIHNldCwgYW5kIGlmIG15IGh5cG90aGVzaXMgaXMgY29ycmVjdCwgSSB3b3VsZCB0ZW5kIHRvIGZpbmQgdGhlc2UgYnVncyBpbiB0aGF0ICJjZW50cmUiIGdyb3VwLCB0aGF0IGlzIGN1cnJlbnQgZGV2b2lkIG9mIHBhdGhvZ2VuIGJpZ3JhbXMuCgpgYGB7ciBtaXNzaW5nUGF0aG9nZW5zLCBtZXNzYWdlPUYsIHdhcm5pbmc9RixlY2hvPUZBTFNFfQpwYXRob2dlbnM8LWMoIm1lYXNsZXMiLCJlYm9sYSIsInppa2EiLCJzeXBoaWxpcyIsICJjaGxhbXlkaWEiLCJnaG9ub3JyaGVhIiwibGVnaW9uZWxsYSIsIm1hbGFyaWEiKQpwYXRob2dlbnM8LXdvcmRTdGVtKHBhdGhvZ2VucykKCmJpZ3JhbV9kZiAlPiUKICBmaWx0ZXIoZ3JlcGwocGFzdGUwKHBhdGhvZ2Vucyxjb2xsYXBzZSA9ICJ8IiksYmlncmFtKSkKICAjZmlsdGVyKGdyZXBsKCJ0dWJlcmN1bHxpbmZsdWVueiIsYmlncmFtKSkgJT4lIFZpZXcoKQoKYGBgCgpTbyB3aGF0IEkgdGhpbmsgaXMgaGFwcGVuaW5nIGhlcmUgaXMgdGhhdCB0aGUgbWV0aG9kcyBvciBldmVuIGNyb3NzLWN1dHRpbmcgdG9waWNzIHNpZ25hbCBpcyBncmVhdGVyIHRoYW4gdGhlIHBhdGhvZ2VuIHNpZ25hbC4gVGhhdCBpcywgInBjciIsInZpcnVzIHNlcXVlbmNlIiBldGMgaGF2ZSBtdWNoIHN0cm9uZ2VyIHNpZ25hbCAoYmVjYXVzZSB0aGVyZSBhcmUgbW9yZSBkb2N1bWVudHMpIGNvbXBhcmVkIHRvICJlYm9sYSB2aXJ1cyIgd2hpY2ggaGFzIGZld2VyIGRvY3VtZW50cywgc28gZXZlcnl0aGluZyBraW5kYSBnZXRzIGx1bXBlZCBpbnRvIGEgZnJlZSBmb3IgYWxsIGluIHRoZSBtaWRkbGUuIEl0IG1ha2VzIHNvbWUgc2Vuc2UsIGJlY2F1c2UgdmlydXNlcyBsaWtlIEVib2xhIG9yIFppa2Egb25seSBiZWNhdXNlIHByaW9yaXRpZXMgZm9yIGludmVzdGlnYXRpb24gYXMgdGhlc2Ugb3V0YnJlYWtzIGJlY2F1c2UgdG8gdGFrZSBvZmYgaW4gMjAxNCAtIDIwMTcuIEJ5IGNvbXBhcmlzb24sIGdlbm9taWMgYW5hbHlzaXMgb2YgVEIsIEhJViwgYW5kIGluZmx1ZW56YSBoYXZlIGEgbXVjaCBsb25nZXIgaGlzdG9yeS4gCgpJdCBjb3VsZCBiZSBwb3NzaWJsZSB0byBmaW5kIGEgdGFnIHVzaW5nICJfdmlydSIsICJfaXNvbCIsIG9yICJfc3RyYWluIiAod2hpY2ggY2FtZSB1cCBhIGxvdCBhcyBwYXR0ZXJucyBpbiB0aGUgbW9yZSBwcm9taW5lbnQgcGF0aG9nZW4gZ3JvdXApIHRvIHNlZSB3aGF0IEkgcHVsbCB1cCBmcm9tIHRoZSAibm9uZS1wYXRob2dlbiIgY2x1c3RlcnMuCgpgYGB7ciBtaXNzaW5nUGF0aG9nZW5zMiwgbWVzc2FnZT1GLHdhcm5pbmc9RixlY2hvPUZBTFNFfQpwYXRob2dlblBhdHRlcm5zIDwtIGMoIl92aXJ1IiwidmlydV8iLCJfaXNvbCIsIl92aXJ1cyIpCgpiaWdyYW1fZGYgJT4lIAogIGZpbHRlcihncmVwbChwYXN0ZTAocGF0aG9nZW5QYXR0ZXJucyxjb2xsYXBzZSA9ICJ8IiksYmlncmFtKSkgJT4lIAogIGdyb3VwX2J5KGJpZ3JhbSx0c25lQ2x1c3Rlck5hbWVzKSAlPiUgCiAgI2dyb3VwX2J5KGJpZ3JhbSkgJT4lIAogIGNvdW50KCkgJT4lIAogIGFycmFuZ2UodHNuZUNsdXN0ZXJOYW1lcykKYGBgCgpUaGlzIGlzIHN0aWxsIGEgYml0IHRvbyBnZW5lcmFsLCBzbyBJIHdpbGwgYnJpbmcgaW4gYSBsaXN0IG9mIGtub3duIGNvbW11bmljYWJsZSBkaXNlYXNlcyB0aGF0IGVmZmVjdCBodW1hbnMsIHRvIGZpbmQgd2hlcmUgdGhleSBhcmUgaW4gdGhlIGRhdGEgc2V0LCBhbmQgaWYgdGhleSBhcmUgcHJlc2VudCwgd2hhdCBiaWdyYW0gcHJlZml4ZXMgb3Igc3VmZml4ZXMgYXJlIGNvbW1vbi4KCmBgYHtyIG1pc3NpbmdQYXRob2dlbnMzLCBtZXNzYWdlPUYsd2FybmluZz1GLGVjaG89RkFMU0V9CnBhdGhvZ2VuTGlzdDwtcmVhZC5jc3YoZmlsZT0iZGF0YS9kaXNlYXNlX2FuZF9wYXRob2dlbnMuY3N2IixoZWFkZXI9VCkKCnBhdGhvZ2VuTGlzdDwtcGF0aG9nZW5MaXN0ICU+JQogIG11dGF0ZShwYXRob2dlbiA9IHN0cnNwbGl0KGFzLmNoYXJhY3RlcihTb3VyY2Uub2YuRGlzZWFzZSksIjsiKSkgJT4lCiAgdGlkeXI6OnVubmVzdChwYXRob2dlbikgJT4lIAogIG11dGF0ZShwYXRob2dlbiA9IHRvbG93ZXIodHJpbXdzKHBhdGhvZ2VuKSkpICU+JSAKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgcGF0aG9nZW4sdG9rZW4gPSAibmdyYW1zIixuPTIpICU+JSAKICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lCiAgbXV0YXRlKHdvcmQxID0gd29yZFN0ZW0od29yZDEpKSAlPiUKICBtdXRhdGUod29yZDIgPSB3b3JkU3RlbSh3b3JkMikpICU+JQogIHVuaXRlKGJpZ3JhbSx3b3JkMSx3b3JkMikKCiN0aGlzIGlzIGZyb20gdGhlIGxpc3QKcGF0aG9nZW5EZiA8LSBpbm5lcl9qb2luKGJpZ3JhbV9kZixwYXRob2dlbkxpc3QpCgojbm93IGxldCdzIGFsc28gYWRkIHRoaXMgdG8gc3R1ZmYgSSd2ZSBhbHJlYWR5IGZvdW5kCmZvdW5kUGF0aG9nZW5EZiA8LSBpbm5lcl9qb2luKGJpZ3JhbV9kZixmaWx0ZXIocGF0aEFubm90LCBBbm5vdGF0ZSA9PSAicGF0aG9nZW4iKSkKCiNub3cgcHV0IHRoZW0gdG9nZXRoZXIgKHJlcHJlc2VudHMgYSB0b3RhbCBvZiA5LDU1MSBhcnRpY2xlcywgb2YgMTUsMzE1ICB0aGF0IGhhdmUgc29tZSBwYXRob2dlbiB0ZXJtKQpwYXRob2dlbkRmPC1mdWxsX2pvaW4ocGF0aG9nZW5EZlssMTo2XSxmb3VuZFBhdGhvZ2VuRGZbLDE6Nl0pCgpwYXRob2dlbkRmJFBNSUQgJT4lIHVuaXF1ZSgpICU+JSBsZW5ndGgoKQoKI25vdyBsZXQncyB0YWtlIGEgbG9vayBob3cgdGhvc2UgYnVncyBhcmUgZGlzdHJpYnV0ZWQKcGF0aG9nZW5EZiAlPiUKICBncm91cF9ieShiaWdyYW0pICU+JQogIGNvdW50KCkKCmBgYAoKClNvLCBicm93c2luZyB0aGVzZSBsYWJlbHMsIHdoYXQgSSBjYW4gdGVsbCBpcyB0aGF0IHRoZXJlIGFyZSBvdGhlciBwYXRob2dlbnMsIHRoZXkncmUganVzdCBub3QgdmVyeSBjb21tb24gaHVtYW4gcGF0aG9nZW5zIG9yIHRoZXkncmUgbm90IGh1bWFuIHBhdGhvZ2VucyBhdCBhbGwsICJjYW5pbmUgcGljb3ZpcnVzIiBmb3IgZXhhbXBsZSwgb3IgdGhleSBhcmUgcmV2aWV3IGFydGljbGVzIGFib3V0IGNlcnRhaW4gZmFtaWxpZXMgb2YgdmlydXNlcy4gU28sIHRoZXkncmUgbm90IHJhbmRvbSBhcnRpY2xlcywgdGhlcmUgYXJlIGp1c3QgdmVyeSBmZXcgYXJ0aWNsZXMgYWJvdXQgdGhvc2UgdG9waWNzIHN1Y2ggdGhhdCB0aGV5IGRvbid0IGZvcm0gYSBzcGVjaWZpYyBjbHVzdGVyLCBhbmQgdGhleSdyZSBub3QgdmVyeSB2ZXJ5IHJlbGV2YW50IGh1bWFuIHBhdGhvZ2VucyBiZWNhdXNlIHRoZXkgYXJlIGFic2VudCBmcm9tIG15IGxpc3QgbGlrZSBFYm9sYSBhbmQgWmlrYSB3aGljaCBzdGlsbCBkb24ndCBoYXZlIGFzIG1hbnkgYXJ0aWNsZXMgcmVsYXRpdmUgdG8gVEIsIGJ1dCB0aGF0IGFyZSBvZiBtb3JlIGNvbnRlbXBvcmFyeSBpbnRlcmVzdC4KCgoKVGhlIG90aGVyIGxhYmVsIHRoYXQgZG9lc24ndCBxdWl0ZSBmaXQgaW50byB0aGUgYXJlYSBvZiBjcm9zcy1jdXR0aW5nIHRvcGljcyBpcyAiZGlzZWFzZSIsIHdoaWNoIGNvdWxkIHJlZmVsZWN0IGEgc3BlY2lmaWMgcGF0aG9nZW4gb3IgY291bGQgcmVmZWxlY3QgYSBjb25kaXRpb24gdGhhdCBkb2VzIGNyb3NzLWN1dCBwYXRob2dlbiBncm91cHMgKGZvciBleGFtcGxlICJyZXNwaXJhdG9yeSBpbmZlY3Rpb25zIiwgYXJlIGNhdXNlZCBieSBhIG11Y2ggb2YgZGlmZmVyZW50IHBhdGhvZ2VucykuIEkgbWFya2VkIHBhdGhvZ2VucyBhbmQgZGlzZWFzZSBkaWZmZXJlbnRseSwgYnV0IG5vdyBJJ2xsIG1ha2UgYW4gYXNzZXNzbWVudCBpZiB0aGF0IHdhcyB0aGUgcmlnaHQgY2hvaWNlLiAKCmBgYHtyIGRpc2Vhc2VJbnZlc3RpZ2F0LCBtZXNzYWdlPUYsd2FybmluZz1GfQoKZGlzZWFzZUFubm90PC1tYW5Bbm5vdCAlPiUKICBmaWx0ZXIoQW5ub3RhdGUgPT0gImRpc2Vhc2UiKSAlPiUKICBzZWxlY3QoYmlncmFtLEFubm90YXRlKQoKZGlzZWFzZUNsdXN0ZXI8LWJpZ3JhbV9kZiAlPiUKICBpbm5lcl9qb2luKGRpc2Vhc2VBbm5vdCkKCmRpc2Vhc2VDbHVzdGVyICU+JQogIHVuZ3JvdXAoKSU+JQogIHNlbGVjdChiaWdyYW0sdHNuZUNsdXN0ZXJOYW1lcykgJT4lCiAgZ3JvdXBfYnkoYmlncmFtLHRzbmVDbHVzdGVyTmFtZXMpJT4lCiAgY291bnQoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dHNuZUNsdXN0ZXJOYW1lcyx5PWJpZ3JhbSkpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9biksY29sb3VyPSJ3aGl0ZSIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsaGp1c3Q9MSx2anVzdD0wLjUpKQpgYGAKCkRpc2Vhc2VzIGFyZSByYXRoZXIgc2ltaWxhciB0byBwYXRob2dlbnMsIEkgbWlnaHQgbm90IHdhbnQgdG8ga2VlcCB0aGlzIGFubm90YXRpb24gYmVjYXVzZSAicGF0aG9nZW5zIiBzZWVtcyB0byBiZSBzdWZmaWNlbnQuIAoKVGhlIHJlc3Qgb2YgdGhlIGFubm90YXRpb25zIGFyZSBhbGwgY3Jvc3MtY3V0dGluZyB0b3BpY3MsIGFuZCBpdCB3b3VsZCBiZSB1c2VmdWwgdG8gc2VlIGhvdyB0aGV5IGFyZSBkaXN0cmlidXRlZCBhY3Jvc3MgdGhlIGRpZmZlcmVudCBncm91cHMsIGFuZCBwYXJ0aWN1bGFyaWx5IGhvdyB0aGV5J3JlIGRpc3RyaWJ1dGVkIHdpdGhpbiB0aGF0IG1pZGRsZSBncm91cC4gCgpgYGB7ciBjcm9zc0N1dHRpbmdUb3BpY3MsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQphbGxBbm5vdDwtbWFuQW5ub3QgJT4lCiAgZmlsdGVyKCEoQW5ub3RhdGUgJWluJSBjKCJkaXNlYXNlIiwicGF0aG9nZW4iKSkpICU+JQogIHNlbGVjdChiaWdyYW0sQW5ub3RhdGUpCgphbGxBbm5vdENsdXN0ZXI8LWJpZ3JhbV9kZiAlPiUKICBpbm5lcl9qb2luKGFsbEFubm90KQoKc29ydE9yZGVyPC1hbGxBbm5vdENsdXN0ZXIgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChBbm5vdGF0ZSx0c25lQ2x1c3Rlck5hbWVzKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGdyb3VwX2J5KEFubm90YXRlKSU+JQogIGNvdW50KCkgJT4lCiAgYXJyYW5nZSgtbikKCgphbGxBbm5vdENsdXN0ZXIgJT4lCiAgdW5ncm91cCgpJT4lCiAgc2VsZWN0KEFubm90YXRlLHRzbmVDbHVzdGVyTmFtZXMpICU+JQogIGdyb3VwX2J5KEFubm90YXRlLHRzbmVDbHVzdGVyTmFtZXMpJT4lCiAgY291bnQoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKEFubm90YXRlPSBmYWN0b3IoQW5ub3RhdGUsbGV2ZWxzID0gYXMuY2hhcmFjdGVyKHNvcnRPcmRlciRBbm5vdGF0ZSkpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dHNuZUNsdXN0ZXJOYW1lcyx5PUFubm90YXRlKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1uKSxjb2xvdXI9IndoaXRlIikrCiAgdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCxoanVzdD0xLHZqdXN0PTAuNSkpCmBgYAoKTG9va2luZyBhdCB0aGUgY3Jvc3MtY3V0dGluZyB0b3BpY3MsIGEgZmV3IHRoaW5ncyBzdGFuZCBvdXQuIFRoZSAibm90LXVzZWZ1bCIsIGFubm90YXRpb24gaXMgZm91bmQgbW9zdCBmcmVxdWVudGx5IGluIHRoZSAiTm9pc2UiIGNsdXN0ZXIsIGJ1dCBvY2N1cnMgYWNyb3NzIGFsbCB0aGUgb3RoZXJzIGFzIHdlbGwuICBEcnVnIHJlc2lzdGFuY2UgYmlncmFtIG9jY3VyIHByaW1hcmlseSBpbiB0aGUgcGF0aG9nZW4gY2x1c3RlcnMsIGFzIHdlbGwgYXMgInN0cmFpbi1pc29sIiBhbmQgIm1yc2EtaXNvbCIgY2x1c3RlcnMuIFZhY2NpbmUgYmlncmFtcyBhcmUgbm90IHByZXNlbnQgaW4gdGhlIGluZmx1ZW56YSBjbHVzdGVycywgd2hpY2ggaXMgYSBiaXQgc3VycHJpc2luZywgYnV0IHRoZSBub2lzZXkgZG9jdW1lbnQgYW5hbHlzaXMgc3VnZ2VzdCB0aGF0IHRoaXMgaXMgYmVjYXVzZSB0aGV5IG1heSBmb3IgdGhlaXIgb3duIGNsdXN0ZXJzIG91dHNpZGUgb2YgdGhlIG1vcmUgZ2VuZXJhbCBpbmZsZW56YSBncm91cC4gCgpOb3cgdGhhdCBJIGhhdmUgYSBzZW5zZSBmcm9tIHRoaXMgYW5hbHlzaXMgdGhhdCBwYXRob2dlbnMgc2hvdWxkIGRyaXZlIHRoZSBhbmFseXNpcywgaXQgd291bGQgYmUgZ29vZCB0byBmaW5kIGNyb3NzLWN1dHRpbmcgdG9waWNzIG1vcmUgZGlyZWN0bHkgbGlua2VkIHRvIHBhdGhvZ2VuIHRlcm1zLiBGb3IgZXhhcGxlICJlYm9sYSBvdXRicmVhayIsICJpbmZsdWVuemFfb3V0YnJlYWsiLCJ0dWJlcmN1bG9zaXMgb3V0YnJlYWsiLCBtYWtpbmcgIm91dGJyZWFrIiBhIHVzZWZ1bCBjcm9zcy1jdXR0aW5nIHRvcGljLiBUaGlzIG1pZ2h0IGJlIG11Y2ggbW9yZSBlZmZlY3RpdmUgdGhlbiBqdXN0IGxvb2tpbmcgZm9yIHRlcm1zIHRoZSBzaWduaWZ5IHJlc2lzdGFuY2UsIGZvciBleGFtcGxlICJob3NwaXRhbCBvdXRicmVhayIsICJjb21tdW5pdHkgb3V0YnJlYWsiLCB3aGljaCBhcmUgY29uY2VwdHMgdGhhdCBjb3VsZCBvY2N1ciB3aXRoaW4gcGF0aG9nZW5zIGNsdXN0ZXJzLiBTbywgSSdsbCB0cnkgdGhhdCBub3cgYW5kIHNlZSB3aGF0IEkgZ2V0CgojIyMgQmlncmFtcyBieSBwYXRob2dlbnMKCkkgd29uJ3QgdXNlIGFsbCBvZiB0aGUgcGF0aG9nZW5zIC0gSSBhbSBnb2luZyB0byB1c2UgdGhlIGRvY3VtZW50cyBmcm9tIHRoZSB0c25lIGNsdXN0ZXJzIHdpdGggcGF0aG9nZW4gYW5ub3RhdGlvbnMgKGFscmVhZHkgZXZpZGVudCBpbiB0aGUgYW5hbHlzaXMpIGFuZCBhbHNvIGFkZCBzb21lIGFkZGl0aW9uYWwgaXRlbXMgdGhhdCBhcmUgbm90IGluIHBhdGhvZ2VuIGNsdXN0ZXJzLCBidXQgYXJlIGludGVyZXN0aW5nIGZyb20gdGhlIHByZXZpb3VzIHBhdGhvZ2VuIGFuYWx5c2lzIChsaWtlIHppa2EsIGVib2xhLCBldGMuKS4KCgpgYGB7ciBmaW5hbFBhdGhvZ2VuRG9jdW1lbnRzLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KI2hvdyBtYW55IGFydGljbGVzIGNvbnRhaW4gc29tZSB0ZXJtIGxpbmtlZCB0byBhIHBhdGhvZ2VuIGJpZ3JhbT8KIzQwIGlzIGFicml0cmFyeSwgYWx0aG91Z2ggSSBwcmVmZXIgY2x1c3RlciBzaXplcyBvZiA1MCwgNDAgZ290IHppa2EgaW4gdGhlcmUsIGFuZCB0aGF0IHdhcyBteSBzdWJqZWN0aXZlIGJlbmNoIG1hcmsKcGF0aG9nZW5zRmluYWw8LXBhdGhvZ2VuRGYgJT4lIHVuZ3JvdXAoKSAlPiUgZ3JvdXBfYnkoYmlncmFtKSAlPiUgY291bnQoKSAlPiUgZmlsdGVyKG4gPiA0MCkKCiNtYWtlIHNvbWUgbWFudWFsIGFubm90YXRpb25zCiN3cml0ZS5jc3YoZmlsZT0iZGF0YS9wYXRob2dlbnNMaXN0RmluYWwuY3N2IixwYXRob2dlbnNGaW5hbCkKCiNub3cgYnJpbmcgdGhhdCBtYW51YWwgbGlzdCBiYWNrIGluLCAKcGF0aG9nZW5zRmluYWw8LXJlYWQuY3N2KGZpbGU9ImRhdGEvcGF0aG9nZW5zTGlzdEZpbmFsX21hbnVhbEFubm90LmNzdiIsaGVhZGVyPVQpCgojSSd2ZSBhbHNvIGFkZGVkIG1ldGFnZW5vbWljcywgbWljcm9iaW9tZSwgb3IgbWljcm9iaW90YSBzaW5jZSB0aGF0J3MgYW4gYXJlYSBvZiB3b3JrIHRvby4KbWV0YUdlbjwtYygibWljcm9iaW9tZSIsIm1ldGFnZW5vbWljcyIsIm1pY3JvYmlvdGEiKQptZXRhR2VuPC13b3JkU3RlbShtZXRhR2VuKQoKbWV0YUdlbm9taWNUZXJtczwtYmlncmFtX2RmICU+JSBmaWx0ZXIoZ3JlcGwocGFzdGUwKG1ldGFHZW4sY29sbGFwc2U9InwiKSxiaWdyYW0pKSAlPiUgZ3JvdXBfYnkoYmlncmFtKSAlPiUgY291bnQoKQptZXRhR2Vub21pY1Rlcm1zJFBhdGhvZ2VuPC1yZXAoIk1pY3JvYmlvdGEiLG5yb3cobWV0YUdlbm9taWNUZXJtcykpCgpwYXRob2dlbnNGaW5hbDwtZnVsbF9qb2luKG1ldGFHZW5vbWljVGVybXNbLGMoImJpZ3JhbSIsIlBhdGhvZ2VuIildLHBhdGhvZ2Vuc0ZpbmFsWyxjKCJiaWdyYW0iLCJQYXRob2dlbiIpXSkKCiNub3RlZCBzb21ldGhpbmdzIGluIHBhdGhvZ2VucyBmaW5hbCB0byBjbGVhbiB1cApwYXRob2dlbnNGaW5hbFtwYXRob2dlbnNGaW5hbCRQYXRob2dlbj09IkhlbGljb2JhY3QgcHlsb3JpIixdJFBhdGhvZ2VuPC0iSGVsaWNvYmFjdGVyIHB5bG9yaSIKcGF0aG9nZW5zRmluYWw8LWZpbHRlcihwYXRob2dlbnNGaW5hbCxQYXRob2dlbiAhPSIiKQoKI2JpZ3JhbV9wYXRoX2RmW2JpZ3JhbV9wYXRoX2RmJFBhdGhvZ2VuPT0iSGVsaWNvYmFjdCBweWxvcmkiLF0kUGF0aG9nZW48LSJIZWxpY29iYWN0ZXIgcHlsb3JpIgoKI25vdyBsZXQncyBmaW5hbGwgZ2V0IGFsbCB0aGUgYXJ0aWNsZXMgdGhhdCBoYXZlIHRoZXNlIGJpZ3JhbSBmb3IgYnVncyBvZiBpbnRlcmVzdApmYXZCdWdzQXJ0aWNsZXM8LWlubmVyX2pvaW4ocGF0aG9nZW5zRmluYWxbLGMoImJpZ3JhbSIsIlBhdGhvZ2VuIildLGJpZ3JhbV9kZikgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3QoUGF0aG9nZW4sUE1JRCkgJT4lCiAgZGlzdGluY3QoKQoKI25vdyBsZXQncyBjb21iaWVuZSB0aGlzIHdpdGggdHNuZUNvcmQsIGxldCdzIHNlZSB3aGF0IEkgYW0gbG9va2luZyBhdC4KCnRzbmVDb3JkUGF0aG9nZW5zPC1sZWZ0X2pvaW4odHNuZUNvcmQsZmF2QnVnc0FydGljbGVzKQp0c25lQ29yZFBhdGhvZ2VucyRQYXRob2dlbltpcy5uYSh0c25lQ29yZFBhdGhvZ2VucyRQYXRob2dlbildPC0iSWdub3JlIgoKI25vdyBsZXQncyBzaG93IHdoZXJlIGl0IGlzCnBhdGhvZ2VuQ2x1c3RlcnMgPC0gdHNuZUNvcmRQYXRob2dlbnMgJT4lCiAgZ3JvdXBfYnkoUGF0aG9nZW4pICU+JQogIGZpbHRlcihQYXRob2dlbiAhPSAiSWdub3JlIikgJT4lCiAgc3VtbWFyaXNlKG1lZFggPSBtZWRpYW4oY29tcDEpLAogICAgICAgICAgICBtZWRZID0gbWVkaWFuKGNvbXAyKSkKCnRzbmVDb3JkUGF0aG9nZW5zICU+JQogIGZpbHRlcihQYXRob2dlbiE9Iklnbm9yZSIpICU+JQogIG11dGF0ZShpc05vaXNlID0gaWZlbHNlKHRzbmVDbHVzdGVyTmFtZXMgJWluJSBjKCJOb2lzZSIsInZlcnlOb2lzZXkiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodHNuZUNsdXN0ZXJOYW1lcyA9PSAiTm9pc2UiLCJVbmNsdXN0ZXJlZCIsIk5ldmVyIENsdXN0ZXJlZCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgLCJDbHVzdGVyZWQiKSklPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbCA9IFBhdGhvZ2VuKSxhbHBoYT0wLjIpKwogIHN0YXRfZWxsaXBzZShhZXMoYWxwaGE9aXNOb2lzZSkpKwogIGxhYnMoeD0idHNuZUNvbXAxIiwKICAgICAgIHk9InRzbmVDb21wMiIpKwogIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMCkpKwogIGdlb21fbGFiZWxfcmVwZWwoZGF0YT1wYXRob2dlbkNsdXN0ZXJzLGFlcyh4PW1lZFgseT1tZWRZLGxhYmVsPVBhdGhvZ2VuLGdyb3VwPVBhdGhvZ2VuKSwKICAgICAgICAgICAgICAgICAgIHNpemU9MyxtaW4uc2VnbWVudC5sZW5ndGggPSB1bml0KDAuMSwibGluZXMiKSkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQoKCgoKYGBgCgoKYGBge3IgZmluYWxQYXRob2dlbkRvY3VtZW50c19QYXBlclZlcnNpb24sZWNobz1GQUxTRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9CgojQ29sb3VyIHRoZSBzdHVmZiBvbiB0aGUgb3V0c2lkZSwgdGhlbiBtYWtlIHNtYWxsIG11bHRpcGxlIHBsb3QgZm9yIHN0dWZmIG9uIHRoZSBzaWRlCm91dHNpZGVTdHVmZjwtYygiRXNjaGVyaWNoaWEgY29saSIsIkFjaW5ldG9iYWN0IGJhdW1hbm5paSIsIkRlbmd1ZSB2aXJ1cyIsIkhhZW1vcGhpbHVzIEluZmx1ZW56YSIsCiAgICAgICAgICAgICAgICAiSGVwYXRpdGlzIEEiLCJIZXBhdGl0aXMgQiIsIkhlcGF0aXRpcyBDIiwiSGVwYXRpdGlzIEUiLCJIZXB0YXRpcyAtIEdlbmVyYWwiLAogICAgICAgICAgICAgICAgIkh1bWFuIEltbXVub2RlZmljeSBWaXJ1cyIsIkh1bWFuIFBhcGlsbG9tYXZpcnVzIiwiSHVtYW4gcm90YXZpcnVzIiwKICAgICAgICAgICAgICAgICJLbGVic2llbGxhIHBuZXVtb25pYSIsIk1FUlMgY29yb25hdmlydXMiLCJNeWNvYmFjdGVyaXVtIHR1YmVyY3Vsb3NpcyIsCiAgICAgICAgICAgICAgICAiU2FsbW9uZWxsYSBFbnRlcmljYSIsIlNhbG1vbmVsbGEgdHlwaGltdXJpdW0iLCJTQVJTIENvcm9uYXZpcnVzIiwiU3RhcGh5bG9jb2NjdXMgYXVyZXVzIiwKICAgICAgICAgICAgICAgICJWaWJyaW8gY2hvbGVyYWUiICkKCnRzbmVDb3JkUGF0aG9nZW5zICU+JQogIGZpbHRlcihQYXRob2dlbiE9Iklnbm9yZSIpICU+JQogIG11dGF0ZShpc05vaXNlID0gaWZlbHNlKHRzbmVDbHVzdGVyTmFtZXMgJWluJSBjKCJOb2lzZSIsInZlcnlOb2lzZXkiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodHNuZUNsdXN0ZXJOYW1lcyA9PSAiTm9pc2UiLCJVbmNsdXN0ZXJlZCIsIk5ldmVyIENsdXN0ZXJlZCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgLCJDbHVzdGVyZWQiKSklPiUKICBtdXRhdGUob3V0c2lkZUdycCA9IGlmZWxzZShQYXRob2dlbiAlaW4lIG91dHNpZGVTdHVmZiwiWWVzIiwiTm8iKSklPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD10c25lQ2x1c3Rlck5hbWVzKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbCA9IG91dHNpZGVHcnApLGFscGhhPTAuMikrCiAgc3RhdF9lbGxpcHNlKGFlcyhhbHBoYT1pc05vaXNlKSkrCiAgbGFicyh4PSJ0c25lQ29tcDEiLAogICAgICAgeT0idHNuZUNvbXAyIikrCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcz1jKDAuNSwwKSkrCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiZGFya2dyYXkiLCJibHVlIikpKwogIGdlb21fbGFiZWxfcmVwZWwoZGF0YT1wYXRob2dlbkNsdXN0ZXJzLGFlcyh4PW1lZFgseT1tZWRZLGxhYmVsPVBhdGhvZ2VuLGdyb3VwPVBhdGhvZ2VuKSwKICAgICAgICAgICAgICAgICAgIHNpemU9MyxtaW4uc2VnbWVudC5sZW5ndGggPSB1bml0KDAuMSwibGluZXMiKSkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICAgICNsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj0gZWxlbWVudF9ibGFuaygpLHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3NhdmUoInBhdGhvZ2VuVmFsaWRhdGlvbkFsbC5wZGYiKQoKCiNzbWFsbCBtdWx0aXBsZXMgaW5zZXQKdG1wPC10c25lQ29yZFBhdGhvZ2VucyAlPiUKICBmaWx0ZXIoUGF0aG9nZW4hPSJJZ25vcmUiKSAlPiUKICBtdXRhdGUoaXNOb2lzZSA9IGlmZWxzZSh0c25lQ2x1c3Rlck5hbWVzICVpbiUgYygiTm9pc2UiLCJ2ZXJ5Tm9pc2V5IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRzbmVDbHVzdGVyTmFtZXMgPT0gIk5vaXNlIiwiVW5jbHVzdGVyZWQiLCJOZXZlciBDbHVzdGVyZWQiKQogICAgICAgICAgICAgICAgICAgICAgICAgICwiQ2x1c3RlcmVkIikpJT4lCiAgbXV0YXRlKG91dHNpZGVHcnAgPSBpZmVsc2UoUGF0aG9nZW4gJWluJSBvdXRzaWRlU3R1ZmYsIlllcyIsIk5vIikpCgogIAp0bXAgJT4lIAogIHNlbGVjdChjb21wMSxjb21wMixQYXRob2dlbiklPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDEseT1jb21wMixncm91cD1QYXRob2dlbikpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMixzaXplPTIpKwogIHN0YXRfZWxsaXBzZShkYXRhPXNlbGVjdCh0bXAsLVBhdGhvZ2VuKSxhZXMoYWxwaGE9aXNOb2lzZSxncm91cD10c25lQ2x1c3Rlck5hbWVzKSkrCiAgZmFjZXRfd3JhcCh+UGF0aG9nZW4sZHJvcD1UUlVFLG5yb3c9NSkrCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcz1jKDAuNSwwKSkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9IGVsZW1lbnRfYmxhbmsoKSxwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKICAKZ2dzYXZlKGZpbGU9InBhdGhvZ2VuU21hbGxNdWx0aXBsZXNBbGwucGRmIixoZWlnaHQ9MTAsd2lkdGg9MTUpCmBgYAoKYGBge3IgZmluYWxQYXRob2dlbkRvY3VtZW50c19QYXBlclZlcnNpb24yLGVjaG89RkFMU0UsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQp0bXAgJT4lCiAgZmlsdGVyKG91dHNpZGVHcnAgPT0gIk5vIiklPiUKICBzZWxlY3QoY29tcDEsY29tcDIsUGF0aG9nZW4pJT4lCiAgZ2dwbG90KGFlcyh4PWNvbXAxLHk9Y29tcDIsZ3JvdXA9UGF0aG9nZW4pKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjIsc2l6ZT0yKSsKICBzdGF0X2VsbGlwc2UoZGF0YT1zZWxlY3QodG1wLC1QYXRob2dlbiksYWVzKGFscGhhPWlzTm9pc2UsZ3JvdXA9dHNuZUNsdXN0ZXJOYW1lcykpKwogIGZhY2V0X3dyYXAoflBhdGhvZ2VuLGRyb3A9VFJVRSxucm93PTUpKwogIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMCkpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yPSBlbGVtZW50X2JsYW5rKCkscGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKQogIApnZ3NhdmUoZmlsZT0icGF0aG9nZW5TbWFsbE11bHRpcGxlc1N1YnNldC5wZGYiLGhlaWdodD03LHdpZHRoPTcpCmBgYAoKRnJvbSB0aGlzIEkgaGF2ZSA2LDM1MCBhcnRpY2xlcyB0aGF0IHJlcHJlc2VudCBzcGVjaWZpYyBwYXRob2dlbnMgdGhhdCBib3RoIGEgdmVyeSB3aWRlbHkgc3R1ZGllZCAoZm9yIHRzbmUgY2x1c3RlcnMpIG9yIGFyZSBsZXNzIHN0dWRpZWQgYnV0IHN0aWxsIGludGVyZXN0aW5nIChsb3cgc2lnbmFsLCBoaWdoIGludGVyZXN0OyBpLmUgZWJvbGEgdmlydXMpLiBJdCdzIGNsZWFyIGZyb20gdGhlIGFib3ZlIGZpZ3VyZSB0aGF0IGRpc2Vhc2UgaW4gdGhlIG1pZGRsZSBjbHVzdGVycyBhcmUgcXVpdGUgc3ByZWFkIG91dCAoYXQgbGVhc3QgYWNjb3JkaW5nIHRvIHRoZSBvcmlnaW5hbCBjbHVzdGVyaW5nKS4gV2UgbWlnaHQgZ2V0IGEgY2xlYW5lciBpbWFnZSBieSByZS1jbHVzdGVyaW5nIHVzaW5nIHRzbmUsIGJ1dCB0aGF0J3Mgbm90IHZlcnkgaW1wb3J0YW50IGZvciBteSBuZXh0IHN0ZXAuCgpTbywgbm93IGxldCdzIHRyeSB0byBiaWcgY3Jvc3MtY3V0dGluZyB0b3BpY3MgYmV0d2VlbiB0aGVzZSBidWdzLiBTb21lIG9mIHRoZXNlIG1pZ2h0IGJlIHNpbWlsYXIgdG8gcHJldmlvdXMgbWFudWFsIGFubm90YXRpb25zIGNyZWF0ZWQgYnkgY29uc2lkZXJpbmcgdGhlIHdob2xlIGRhdGEgc2V0LCBidXQgYWxzbyBhbm5vdGFpdG9uIG9mIHRvcGljcyBsaW5rZWQgdG8gc3BlY2lmaWMgdGVybXMgKGkuZS4gImVib2xhIG91dGJyZWFrIiwgaW5zdGVhZCBvZiAiaG9zcGl0YWwgb3V0YnJlYWsiKS4KCmBgYHtyIGZpbmFsUGF0aG9nZW5Eb2N1bWVudHMyLCBtZXNzYWdlPUYsd2FybmluZz1GfQoKYmlncmFtX3BhdGhfZGY8LWlubmVyX2pvaW4oYmlncmFtX2RmLGZhdkJ1Z3NBcnRpY2xlc1ssYygiUE1JRCIsIlBhdGhvZ2VuIildKSAKCiNvdGhlciB0b3BpY3MKY3Jvc3NUb3BpYzwtYmlncmFtX3BhdGhfZGYlPiUKICBmaWx0ZXIoUGF0aG9nZW4gIT0iSWdub3JlIikgJT4lCiAgZ3JvdXBfYnkoYmlncmFtLFBhdGhvZ2VuKSU+JQogIGNvdW50KCkgJT4lIAogIGZpbHRlcihubiA+IDIpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShiaWdyYW0pICU+JQogIGNvdW50KCkgJT4lCiAgbXV0YXRlKHRvcGljID0gYmlncmFtKSAlPiUKICBzZXBhcmF0ZSh0b3BpYyxjKCJ3b3JkMSIsIndvcmQyIiksIl8iKQoKCiNuZXh0LCByZW1vdmUgdGVybXMgdGhhdCBvbmx5IG9jY3VyIGluIG9uZSBjbHVzdGVyLiBOb3cgdGhhdCBJIGtub3cgd2hhdCB0aGUgcGF0aG9nZW5zIGFyZSwgSSBkb24ndCBuZWVkIHRoZSBwYXRob2dlbiAKCndyaXRlLmNzdihmaWxlPSJkYXRhL3RvcGljc0J5UGF0aG9nZW4uY3N2Iixjcm9zc1RvcGljLHF1b3RlPUYpCmBgYAoKV2hlbiBJIGxvb2sgYXQgdGhlIGNyb3NzLWN1dHRpbmcgdG9waWNzLCBJIGNhbiBzZWUgdGhlIGNvbW1vbiB0aGVtZXMsIG11Y2ggbW9yZSBjbGVhcmx5IHdoZW4gY29uc2lkZXJpbmcgcGF0aG9nZW4gZG9jdW1lbnRzIGluc3RlYWQgb2YganVzdCB0aGUgdHNuZSBjbHVzdGVycyBhcyBJIGhhZCBjb25zaWRlcmVkIHByZXZpb3VzbHkuIAoKSSdsbCBub3cgbG9hZCB0aGUgbWFudWFsIGFubm90YXRpb25zIHRvIHNlZSBob3cgYmlncmFtIGFuZCBhbm5vdGF0aW9ucyBwbGF5IG91dCBhY3Jvc3MgdGhlIGRpZmZlcmVudCBjbHVzdGVycywgYW5kIGZpbmFsbHksIGhvdyBtYW55IGRvY3VtZW50cyBjb250YWluIHNvbWUgdXNlZnVsIGFubm90YXRpb25zIChzb21lIGJpZ3JhbXMgYXJlIHRhZ2dlZCBhcyBpZ25vcmUpLiBUaGUgYW5ub3RhdGlvbnMgd2VyZSBzdWJqZWN0aXZlbHkgc2VsZWN0ZWQsIG90aGVycyBtYXkgZmluZCBpdGVtcyBJJ3ZlIHRhZ2dlZCBhcyAiaWdub3JlIiB0byBiZSB2ZXJ5IGludGVyZXN0aW5nLiBCdXQgZ2VuZXJhbGx5LCBpdGVtcyB0aGF0IHdlcmUgbGFiZWxsZWQgYXMgImlnbm9yZSIgaGFkIHRvIGRvIHdpdGggY29tbW9uIHBocmFzaW5nICgiYW5hbHlzaXMgcmV2ZWFsZWQiKSwgZ2VuZXJpYyBtb2xlY3VsYXIgYmlvbG9neSBtZWNoYW5pc21zIChsaWtlIHJlcG9yZHVjdGl2ZSBtZWNoYW5pc21zOyBub3RlLCBtZWNoYW5pc21zIHNwZWNpZmljIHRvIGRydWcgcmVzaXN0YW5jZSAocGxhc21pZHMsIHJlc2lzdGFuY2UgZ2VuZXMpIHdlcmUgcmV0YWluZWQgYW5kIHRhZ2dlZCBhcyBtQmlvKSwgZ2VuZXJpYyBpbW11bmUgcmVzcG9uc2VzIChobGEgdHlwZSwgaW5uYXRlIGltbXVuaXR5OyB0aGVzZSBkb24ndCBoYXZlIG11Y2ggdG8gZG8gd2l0aCBlcGlkZW1pb2xvZ3kgZXZlbiB0aG91Z2ggdGhleSBhcmUgcmVsZXZhbnQgdG8gdGhlIGRpc2Vhc2UpOyByZWdpb25zIChBZnJpY2EsIENoaW5hLCBVU0EgZWN0Lik7IGFuZCByZS1pdGVyYXRpb25zIG9mIGFscmVhZHkgaWRlbnRpZmllZCBwYXRob2dlbnMuIEZpbmFsbHkgc29tZSB0ZXJtcyB3ZXJlIGp1c3QgdmVyeSBicm9hZCwgYW5kIG90aGVyIG1vcmUgc3BlY2lmaWMgdGVybXMgd2VyZSBzb21ldGltZXMgYmV0dGVyIHRvIHVzZS4gCipub3RlIHRvIHNlbGY6IHVzZSB0aGUgYmlncmFtIGRhdGFzZXQgdGhhdCBoYXNuJ3QgYmVlbiBmaWx0ZXJlZCBmb3IgZGlmZmVyZW50IGxldmVscyBvZiBub2lzZSBiZWNhdXNlIHRoZXJlIGFyZSB1c2VmdWwgYXJ0aWNsZXMgZmxvYXRpbmcgYXJvdW5kKgpgYGB7ciB3aHlBbm5vdGF0aW9uc01hbnVhbCwgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KbWFuQW5ub3Q8LXJlYWQuY3N2KGZpbGU9ImRhdGEvdG9waWNzQnlQYXRob2dlbl9tYW51YWxsQW5ub3QuY3N2IixoZWFkZXI9VCkKCgojTm93LCBzaW5jZSBJIGhhdmUgdGhlIGJpZ3JhbXMgZm9yIGFsbCB0aGUgdXNlZnVsIGJ1Z3MgZmlsdGVyZWQgb3V0IGZyb20gdGhlIG5vaXNleSBkb2N1bWVudHMsIEkgd2lsbCBpbmNsdWRlIGV2ZXJ5IHNpbmdsZSBkb2N1bWVudCAod2l0aCBhbGwgbGV2ZWxzIG9mIG5vaXNlKSB0aGF0IG1hcCB0byBhIGRvY3VtZW50IAoKI25vdyBnZXQgdG90YWwgbnVtYmVyIG9mIGJ1Z3MKdG90YWxCdWdzPC1wYXRob2dlbnNGaW5hbCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIoUGF0aG9nZW4gIT0gIklnbm9yZSIpICU+JSBzZWxlY3QoUGF0aG9nZW4pICU+JSB1bmlxdWUoKSAlPiUgY291bnQoKQp0b3RhbEJ1Z3M8LXRvdGFsQnVncyRuCgojZmlyc3QsIHRoZSBtb3N0IGNvbW1vbiBiaWdyYW1zIGFuZCBob3cgbWFueSBwYXRob2dlbnMgdGhleSBhcmUgcHJlc2VudCBpbgptYW5Bbm5vdCAlPiUKICBmaWx0ZXIobj4xKSU+JQogIGdyb3VwX2J5KG4pICU+JQogIGNvdW50KCkgJT4lIAogIGdncGxvdChhZXMoeD1uLHk9bm4pKSsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKwogIGdlb21fdGV4dChhZXMobGFiZWw9bm4pLG51ZGdlX3k9NSxjb2w9InJlZCIpKwogIHhsYWIoIlRvdGFsICMgb2YgcGF0aG9nZW5zIGJpZ3JhbSBvY2N1cnMgaW4iKSsKICBnZ3RpdGxlKCJCaWdyYW1zIGFjcm9zcyBwYXRob2dlbnMiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPTI6dG90YWxCdWdzKSsKICB0aGVtZV9idygpCmBgYAoKVGhlIG1ham9yaXR5IG9mIHRlcm1zIG9jY3VyIGluIGp1c3Qgb25lIHBhdGhvZ2VuICh+MSwzMDAgYmlncmFtcyksIHdoaWxlIHNvbWUgb2NjdXIgaW4gYWxsIHBhdGhvZ2VucyAoICJnZW5vbWUgc2VxdWVuY2UiKS4gQmlncmFtcyBhcmUgYWxzbyBzcGVjaWZpYyB0ZXJtcyAoZXNwZWljYWxseSB0aG9zZSB0aGF0IGp1c3Qgb2NjdXIgaW4gb25lIHBhdGhvZ2VuKSB0aGF0IGNhbiBkZXNjcmliZSB0aGUgc2FtZSBjb25jZXB0LiBGb3IgZXhhbXBsZSAiQmV0YSBsYWN0YW1hc2UiIGlzIGFzc29jaWF0ZWQgd2l0aCBkcnVnIHJlc2lzdGFuY2UsIGJ1dCBtaWdodCBiZSByZWZlcmVuY2VkIGluIGRpZmZlcmVudCB3YXlzIChhYmJyZXZpYXRpb24gZm9yIGV4YW1wbGUpLiBTbyBJIG1hbnVhbGx5IGFubm90YXRlZCBlYWNoIG9mIHRoZXNlIGJpZ3JhbXMgdG8gY29uY2VwdHMgdGhhdCB0aGV5IGRlc2NyaWJlLiAKCmBgYHtyIHdoeUFubm90YXRpb25zTWFudWFsMixtZXNzYWdlPUYsd2FybmluZz1GfQptYW5Bbm5vdCAlPiUKICBncm91cF9ieSh3aHkpICU+JQogIGNvdW50KCkgJT4lIAogIGZpbHRlcih3aHkgIT0iaWdub3JlIikgJT4lCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIod2h5LC1ubikseT1ubikpKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrCiAgdGhlbWVfYncoKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPW5uKSxudWRnZV95PTUsY29sPSJyZWQiKSsKICBnZ3RpdGxlKCJBbm5vdGF0aW9ucyBmb3IgQmlncmFtcyIpKwogIHhsYWIoIkFubm90YXRpb24gdGVybXMiKSsKICB5bGFiKCJUb3RhbCBudW1iZXIgb2YgYmlncmFtcyBhc3NpZ25lZCB0byBhbm5vdGF0aW9uIHRlcm0iKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCx2anVzdD0wLjUsaGp1c3Q9MSkpCmBgYApUbyBzb21lIGJpZ3JhbXMgSSBoYXZlIGdpdmVuIG1vcmUgdGhhbiBvbmUgYW5ub3RhdGlvbiBzaW5jZSBpdCBjb3VsZCByZXByZXNlbnQgbXVsdGlwbGUgY29uY2VwdHMuIEluIG15IG1pbmQgdGhlcmUgaXMgYSBoaWVhcmNoeSBoZXJlLCBidXQgSSdsbCBicmVhayB0aGVtIHVwLgoKYGBge3Igd2h5QW5ub3RhdGlvbnNNYW51YWwzLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9Cm1hbkFubm90ICU+JQogIG11dGF0ZSh3aHkgPSBzdHJzcGxpdChhcy5jaGFyYWN0ZXIod2h5KSxzcGxpdD0iOyIpKSAlPiUKICB1bm5lc3Qod2h5KSU+JSAKICBncm91cF9ieSh3aHkpICU+JQogIGNvdW50KCkgJT4lIAogIGZpbHRlcih3aHkgIT0iaWdub3JlIikgJT4lCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIod2h5LC1ubikseT1ubikpKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrCiAgdGhlbWVfYncoKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPW5uKSxudWRnZV95PTUsY29sPSJyZWQiKSsKICBnZ3RpdGxlKCJCaWdyYW1zIHBlciBjb25jZXB0IikrCiAgeGxhYigiQ29uY2VwdCB0ZXJtcyIpKwogIHlsYWIoIlRvdGFsIG51bWJlciBvZiBiaWdyYW1zIGFzc2lnbmVkIHRvIGFubm90YXRpb24gdGVybSIpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLHZqdXN0PTAuNSxoanVzdD0xKSkKYGBgClNvIG1vc3QgYW5ub3RhdGlvbnMgKGNvbmNlcHRzKSBoYXZlIGEgbnVtYmVyIG9mIGJpZ3JhbXMgYXNzb2NpYXRlZCB3aXRoIHRoYXQgY29uY2VwdCwgd2l0aCBvbmx5IGEgZmV3IChjb21tdW5pdHksIGludGVybmF0aW9uYWwsIG91dGNvbWUsIGFuZCByZXNlcnZvaXIpIGhhdmUgb25seSBvbmUgYXNzb2NpYXRlZCBiaWdyYW0uIFRoZSBhbm5vdGF0aW9ucyBoZXJlIHdlcmUgbXVjaCBlYXNpZXIgdG8gY29tZSB1cCB0aGFuIG15IGluaXRpYWwgYXR0ZW1wdCB0aGF0IHVzZWQganVzdCB0aGUgY2x1c3RlcnMgYW5kIG5vdCB0aGUgcGF0aG9nZW4gKGFnYWluLCBldmVuIHRob3VnaCB0aGVyZSBpcyBhbiBvdmVybGFwIGJldHdlZW4gY2x1c3RlcnMgYW5kIHBhdGhvZ2VucykuCgpGaW5hbGx5LCBJIHdpbGwgbm93IHNob3cgaG93IHRoZXNlIGNvbmNlcHRzIG9jY3VyaW5nIGFjY3Jvc3MgdGhlIGRpZmZlcmVudCBwYXRob2dlbnMuCgpgYGB7ciB3aHlBbm5vdGF0aW9uc0J5UGF0aG9nZW4sIG1lc3NhZ2U9Rix3YXJuaW5nPUZ9CmJpZ3JhbV9wYXRoX2RmJT4lCiAgZmlsdGVyKFBhdGhvZ2VuICE9Iklnbm9yZSIpICU+JQogIGlubmVyX2pvaW4obWFuQW5ub3RbLGMoImJpZ3JhbSIsIndoeSIpXSkgJT4lIAogIGZpbHRlcih3aHkgIT0iaWdub3JlIikgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKHdoeSA9IHN0cnNwbGl0KGFzLmNoYXJhY3Rlcih3aHkpLHNwbGl0PSI7IikpICU+JQogIHVubmVzdCh3aHkpJT4lIAogIGdyb3VwX2J5KHdoeSxQYXRob2dlbiklPiUKICBjb3VudCgpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieSh3aHkpICU+JQogIGNvdW50KCkgJT4lIAogIGdncGxvdChhZXMoeD1yZW9yZGVyKHdoeSwtbikseT1uKSkrCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSsKICB0aGVtZV9idygpKwogIGdlb21fdGV4dChhZXMobGFiZWw9biksbnVkZ2VfeT0xLGNvbD0icmVkIikrCiAgeGxhYigiQ29uY2VwdCIpKwogIHlsYWIoIiMgb2YgUGF0aG9nZW5zIHdpdGggdGhpcyBjb25jZXB0IikrCiAgZ2d0aXRsZSgiQ29uY2VwdHMgYnkgUGF0aGdlbnMiKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCx2anVzdD0wLjUsaGp1c3Q9MSkpCmBgYApTbyB0aGVzZSBjb25jZXB0cyBoYXZlIG11Y2ggbW9yZSBjb3ZlcmFnZSB0aGFuIGluZGl2aWR1YWwgYmlncmFtIHRlcm1zLiBMYXN0eSwgaXQgaXMgcG9zc2libGUgdG8gc2VlIGhvdyBtYW55IGFydGljbGVzLCBwZXIgY29uY2VwdCBhbmQgcGF0aG9nZW4gZXhpc3QuIFVwIHRvIHRoaXMgcG9pbnQgaW4gdGhlIGFuYWx5c2lzLCBJJ3ZlIHJlbW92ZWQgdmVyeSBub2lzZXkgYXJ0aWNsZXMgdGhhdCByZWFsbHkgZGlkbid0IGNsdXN0ZXIgd2l0aCBvdGhlcnMuIEkgd2lsbCBub3cgYnJpbmcgdGhvc2UgYXJ0aWNsZXMgYmFjayBpbiAoZXhjZXB0IHRoZSB2ZXJ5IG5vaXNleSBkb2N1bWVudHMpLCBhbmQgaWYgdGhleSBjb250YWluIGJpZ3JhbXMgdGhhdCByZWxhdGUgdG8gd2hhdCBJJ3ZlIGZvdW5kLCB0aGVuIHRoZXkncmUgZmFpciBnYW1lLgoKYGBge3Igd2h5QW5ub3RhdGlvbnNCeVBhdGhvZ2VuMiwgbWVzc2FnZT1GLHdhcm5pbmc9Rn0KCmRhdDwtYmlncmFtX3BhdGhfZGYlPiUKICBmaWx0ZXIoUGF0aG9nZW4gIT0iSWdub3JlIikgJT4lCiAgaW5uZXJfam9pbihtYW5Bbm5vdFssYygiYmlncmFtIiwid2h5IildKSAlPiUgCiAgZmlsdGVyKHdoeSAhPSJpZ25vcmUiKSAlPiUgCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZSh3aHkgPSBzdHJzcGxpdChhcy5jaGFyYWN0ZXIod2h5KSxzcGxpdD0iOyIpKSAlPiUKICB1bm5lc3Qod2h5KQoKc2F2ZVJEUyhmaWxlPSJkYXRhL2NvbmNlcHRzX2JpZ3JhbXNfYXJ0aWNsZXNfRmluYWwuUkRTIixkYXQpCgpkYXQgJT4lCiAgc2VsZWN0KFBNSUQsd2h5LFBhdGhvZ2VuKSAlPiUKICBncm91cF9ieSh3aHksUGF0aG9nZW4pICU+JQogIGRpc3RpbmN0KCkgJT4lIAogIGNvdW50KCklPiUKICBmaWx0ZXIobj4xKSAlPiUgI3JlbW92ZSBjb25jZXB0cyB0aGF0IG9jY3VycyBvbmx5IGluIG9uZSBhcnRpY2xlICh2ZXJ5IGxpdHRsZSBzaWduYWwgb3Igc3VwcG9ydCkKICBnZ3Bsb3QoYWVzKHggPSB3aHkseT1QYXRob2dlbikpKyAjbm90IHBlcmZlY3QsIGJ1dCBnb29kIGVub3VnaAogIGdlb21fdGlsZShhZXMoZmlsbD1uKSxjb2xvdXI9IndoaXRlIikrCiAgdGhlbWVfYncoKSsKICB4bGFiKCJDb25jZXB0IikrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsaGp1c3Q9MSx2anVzdD0wLjUpKQpgYGAKCkZyb20gdGhlIGFib3ZlIGZpZ3VyZSwgc3RoZSBwcmVzZW5jZSBhbmQgYWJzZW5jZSBvZiBzb21lIGNvbmNlcHRzIG1ha2Ugc2Vuc2UuIEZvciBleGFtcGxlLCB0aGUgImNhbmNlciIgY29uY2VwdCBpcyBmb3VuZCBpbiAiSC4gcGx5b3JpLCBIQ1YsSEJWLCBhbmQgSGVwYXRpdGlzLUdlbmVyYWwsYW5kIEhQViIgd2hpY2ggbWFrZSBzZW5zZSBhcyB0aGVzZSBwYXRob2dlbnMgYXJlIG1vcmUgZGlyZWN0bHkgaW52b3ZsZWQgaW4gdHVtb3VyIGFldGlvbG9neS4gVGhlIHRlcm1zLCAiUGh5bG9nZW55IG91dGJyZWFrLGdlbm9tZSxjaGFyYWN0ZXJpemF0aW9uIiBhbmQgZHJ1ZyByZXNpc3RhbmNlIG9jY3VyIGluIGEgbG90IG9mIHBhdGhvZ2Vucy4gVGhlcmUgYXJlIHNvbWUgaW50ZXJlc3RpbmcgY29uY2VwdHMgcmVwcmVzZW50ZWQgaW4gc29tZSBwYXRob2dlbnMuIEZvciBleGFtcGxlICJ0cmFuc21pc3Npb24iIGluIG1pY3JvYmlvdGEsIHdoaWNoIHNlZW1zIG9kZCwgYnV0IGxvb2tpbmcgZGlnZ2luZyBpbnRvIGl0IGEgYml0IHNlZW1zIHRvIGJlIGxlZ2l0bWF0ZSBiZWNhdXNlIHRoZSBwYXBlcnMgY29uY2VybnMgaG93IG1pY3JvYmlvdGEgY29uY2VybiBzdXNjZXB0aWJpbGl0eSBmb3IgdHJhbnNtaXNzaW9uIGZvciBvdGhlciBwYXRob2dlbnMuIFdoaWNoIGlzIGludGVyZXN0aW5nLCBidXQgaXMgZGlmZmVyZW50IGZyb20gc2F5IHRoZSBjaGFpbiBvZiB0cmFuc21pc3Npb24gd2l0aCBvdGhlciBwYXRob2dlbnMuIFNvIHRoZXJlJ3Mgc3RpbGwgYSBmYWlyIGJpdCBvZiBudWFuY2UuIFRoZXJlJ3MgYWxzbyBzb21lIGFydGljbGVzIHRoYXQgd2lsbCBiZSB2ZXJ5IG1ldGhvZHMgaGVhdnkgKGxpa2UgdGhvc2UgZmxhZ2dlZCBhcyBjaGFyYWN0ZXJpemF0aW9uIGFuZCBtQmlvKSwgd2hpY2ggcmVhbGx5IGRvZXNuJ3QgaGF2ZSBtdWNoIHRvIGRvIHdpdGggZ2Vub21pYyBlcGlkZW1pb2xvZ3ksIGJ1dCBtb3JlIGFib3V0IGxhYm9yYXRvcnkgdGVjaG5pcXVlcy4gIFRob3NlIGRvbid0IGhhdmUgbXVjaCB0byBkbyB3aXRoIHZpc3VhbGl6YXRpb24sIGJ1dCBtYXkgY29udGFpbiBzb21lIGludGVyZXN0aW5nIG9mIHVzZWZ1bCB2aXN1YWxpemF0aW9ucy4KCkZpbmFsbHksIEkgd2FudCB0byBrbm93LCBob3cgbWFueSAiY29uY2VwdHMiIiB0aGVyZSBhcmUgcGVyIGFydGljbGUuIFdvdWxkIHNvbWUgYXJ0aWNsZXMgb25seSBoYXZlIG9uZSBjb25jZXB0LCB3b3VsZCBvdGhlciBhcnRpY2xlcyBoYXZlIG1vcmUgdGhhbiBvbmUuIAoKYGBge3IgY29uY2VwdHNCeUFydGljbGVzLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9CgpkYXQgJT4lCiAgc2VsZWN0KFBNSUQsIHdoeSkgJT4lCiAgZGlzdGluY3QoKSAlPiUgI3NvbWUgYXJ0aWNsZXMgbWF5IGNvbnRhaW4gYmlncmFtcyB0aGF0IG1hcCB0byB0aGUgc2FtZSBjb25jZXB0IG11bHRpcGxlIHRpbWVzCiAgZ3JvdXBfYnkoUE1JRCkgJT4lCiAgY291bnQoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PW4pKSsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9MSxjb2xvdXI9IndoaXRlIikrCiAgdGhlbWVfYncoKQpgYGAKVGhlIG1ham9yaXR5IG9mIGFydGljbGVzIGFyZSBsaW5rZWQgdG8gb25seSBvbmUgdG8gdGhyZWUgaW50ZXJlc3RpbmcgY29uY2VwdHMuIFRoZSBhcnRpY2xlcyB0aGF0IGhhdmUgbW9yZSBjYW4gYmUgaW50ZXJlc3RpbmcsIGJ1dCBkb24ndCBhbHdheXMgaGF2ZSBmdWxsIHRleHQgbGlua3MuIFdlIGNhbiBhbHNvIHNwbGl0IHRoaXMgdXAgYnkgcGF0aG9nZW4gdG8gc2VlIHdoaWNoIHBhdGhvZ2VuIGhhcyBwYXBlcnMgd2l0aCBhIHNpbmdsZSBjb25jZXB0LgoKCgpgYGB7ciBjb25jZXB0c0J5QXJ0aWNsZXMyLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9CmRhdCAlPiUKICBzZWxlY3QoUE1JRCwgd2h5LFBhdGhvZ2VuKSAlPiUKICBkaXN0aW5jdCgpICU+JSAjc29tZSBhcnRpY2xlcyBtYXkgY29udGFpbiBiaWdyYW1zIHRoYXQgbWFwIHRvIHRoZSBzYW1lIGNvbmNlcHQgbXVsdGlwbGUgdGltZXMKICBncm91cF9ieShQTUlELFBhdGhvZ2VuKSAlPiUKICBjb3VudCgpICU+JQogIGdncGxvdChhZXMoeD1uKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPTEsY29sb3VyPSJ3aGl0ZSIpKwogIGZhY2V0X3dyYXAoflBhdGhvZ2VuLHNjYWxlcz0iZnJlZV95IikrCiAgdGhlbWVfYncoKQpgYGAKTm90IHZlcnkgc3VycHJpc2luZ2x5LCB0aGUgcGF0aG9nZW5zIHRoYXQgd291bGQgcHJldHR5IG11Y2ggZm9ybSB0aGVpciBvd24gY2x1c3RlcnMgb24gVFNORSBhbmFseXNpcyBhbHNvIGhhdmUgYSBsb3Qgb2YgY29uY2VwdHMgcmVwcmVzZW50ZWQgYW1vbmcgdGhlbSwgYW5kIHRoaXMgaXMgcmVhbGx5IGJlY2F1c2UgdGhlcmUncyBiZWVuIGVub3VnaCBwdWJsaXNoZWQgYWJvdXQgdGhlbSBvdmVyIHZlcnkgbWFueSB5ZWFycy4gU28gd2hhdCBJIHdpbGwgZG8gaXMga2VlcCB0aGF0IHBhdGhvZ2VucyB3aXRoIGEgbG90IG9mIGNvbmNlcHRzIGFzIHRoZWlyIG93biBjbHVzdGVycywgYW5kIGV2ZXJ5dGhpbmcgZWxzZSwgSSB3aWxsIHBsYWNlIGludG8gYW4gIm90aGVyIiBjYXRlZ29yeS4KCmBgYHtyIGNvbmNlcHRzQnlBcnRpY2xlczMsIG1lc3NhZ2U9Rix3YXJuaW5nPUZ9Cgp0b3RhbF9kb2NzIDwtIGRhdCAlPiUgCiAgc2VsZWN0KFBNSUQsIFBhdGhvZ2VuKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGdyb3VwX2J5KFBhdGhvZ2VuKSU+JQogIHN1bW1hcml6ZSh0b3RhbCA9IG4oKSkgJT4lIAogIGZpbHRlcih0b3RhbD4xMDApICU+JSAKICBzZWxlY3QoUGF0aG9nZW4pCgojIGtlZXBHcm91cDwtZGF0ICU+JQojICAgc2VsZWN0KFBNSUQsIHdoeSxQYXRob2dlbikgJT4lCiMgICBkaXN0aW5jdCgpICU+JSAjc29tZSBhcnRpY2xlcyBtYXkgY29udGFpbiBiaWdyYW1zIHRoYXQgbWFwIHRvIHRoZSBzYW1lIGNvbmNlcHQgbXVsdGlwbGUgdGltZXMKIyAgIGdyb3VwX2J5KFBNSUQsUGF0aG9nZW4pICU+JQojICAgc3VtbWFyaXNlKG5Db25jZXB0cyA9IG4oKSkgJT4lCiMgICB1bmdyb3VwKCklPiUKIyAgIGdyb3VwX2J5KFBhdGhvZ2VuLG5Db25jZXB0cyklPiUKIyAgIHN1bW1hcmlzZSh0b3RhbERvY3MgPSBuKCkpICU+JSAKIyAgIGlubmVyX2pvaW4odG90YWxfZG9jcykgJT4lIAojICAgdW5ncm91cCgpICU+JQojICAgbXV0YXRlKGZyZXEgPSB0b3RhbERvY3MvdG90YWwpICU+JSAKIyAgIGZpbHRlcihuQ29uY2VwdHMgPT0gMSkgJT4lIAojICAgZmlsdGVyKGZyZXE8MC4yNSkgJT4lICNmZXdlciB0aGFuIDI1JSBvZiBhcnRpY2xlcyBhcmUgbGlua2VkIHRvIG9ubHkgb25lIGNvbmNlcHQKIyAgIHNlbGVjdChQYXRob2dlbikKCmRhdCAlPiUKICBzZWxlY3QoUE1JRCwgd2h5LFBhdGhvZ2VuKSAlPiUKICBkaXN0aW5jdCgpICU+JSAjc29tZSBhcnRpY2xlcyBtYXkgY29udGFpbiBiaWdyYW1zIHRoYXQgbWFwIHRvIHRoZSBzYW1lIGNvbmNlcHQgbXVsdGlwbGUgdGltZXMKICBtdXRhdGUoUGF0aG9nZW4gPSBmYWN0b3IoaWZlbHNlKFBhdGhvZ2VuICVpbiUgdG90YWxfZG9jcyRQYXRob2dlbiwgUGF0aG9nZW4sICJPdGhlciIpLGxldmVscz1jKHRvdGFsX2RvY3MkUGF0aG9nZW4sIk90aGVyIikpKSAlPiUKICBncm91cF9ieShQTUlELFBhdGhvZ2VuKSAlPiUKICBjb3VudCgpICU+JQogIGdncGxvdChhZXMoeD1uKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPTEsY29sb3VyPSJ3aGl0ZSIpKwogIGZhY2V0X3dyYXAoflBhdGhvZ2VuKSsKICB0aGVtZV9idygpCmBgYAoKV2hlbiBJIHNhbXBsZSBhcnRpY2xlcywgSSB3aWxsIHRyeSB0byBzYW1wbGUgY29uY2VwdHMgZnJvbSBhY3Jvc3MgMTggZ3JvdXBzLCB0aGlzIHdpbGwgcmVzdWx0IGluIGFib3V0IDQxNCBwdWJsaWNhdGlvbnMuIElmIGEgcGFwZXIgdGhhdCBpcyBzYW1wbGVkIGVuZHMgdXAgbm90IGhhdmluZyBhIHJlbGV2YW50IHZpc3VhbGl6YXRpb24sIHRoZW4gSSB3aWxsIHNhbXBsZSBhZ2FpbiAoc3BlY2lmaWNhbGx5IGZvciB0aGF0IHBhdGhvZ2VuIGFuZCBjb25jZXB0KSB1bnRpbCBJIGdldCBvbmUgdGhhdCBkb2VzLgoKCiNTYW1wbGluZyBkb2N1bWVudHMKQmFzZWQgdXBvbiB0aGUgYWJvdmUgYW5hbHlzaXMsIEkndmUgY29uY2x1ZGVkIHRoYXQgaXQncyBlYXNpZXN0IHRvIHRyZWF0IHRoZSBjb25jZXB0cyBhcyB0YWdzIHJhdGhlciB0aGFuIGFzIGhhdmluZyBzb21lIG1vcmUgaW5uYXRlIGhpZWFyY2hpY2FsIHN0cnVjdHVyZS4KCk15IHN0cmF0ZWd5IHRvIHNhbXBsZSBkb2N1bWVudHMgaXMgdG8gbWFrZSBzdXJlIGV2ZXJ5IGNvbmNlcHQgaXMgY292ZXJlZCBwZXIgcGF0aG9nZW4uIEluIHRoZW9yeSwgaWYgZWFjaCBwYXBlciBoYWQgb25seSBvbmUgYWZmaWxpYXRlZCB0YWcsIEkgd291bGQgbmVlZCB0byBzYW1wbGUgMjMgdW5pcXVlIHBhcGVycyBmb3IgMTggcGF0aG9nZW5zLCByZXN1bHRpbmcgaW4gYSBmaW5hbCBkYXRhc2V0IG9mIDQxNCBwYXBlcnMgdGhhdCBmb3IgdGhlIGZvbGxvd2luZyBhbmFseXNpcyBvZiB2aXN1YWxpemF0aW9ucy4gU2luY2UgcGFwZXJzIHRlbmQgdG8gaGF2ZSBtb3JlIHRoYW4gb25lIGNvbmNlcHQgYWZmaWxpYXRlZCB3aXRoIHRoZW0sIHRoZSB0b3RhbCBudW1iZXIgb2YgcGFwZXJzIHdpbGwgYWN0dWFsbHkgYmUgcXVpdGUgYSBiaXQgZmV3ZXIuIEkgcGVyc29uYWxseSBhbSBhaW1pbmcgZm9yIGEgZmV3IGh1bmRyZWQgZG9jdW1lbnRzIHNvIHRoYXQgSSBoYXZlIGEgZ29vZCBkaXZlcnNpdHkgZm9yIGFuYWx5c2lzLCBhbmQgc28sIEkgbWlnaHQgZG8gcmVkdW5kYW50IGNvdmVyYWdlIG9mIGNvbmNlcHRzIHdpdGggYSBwYXRob2dlbiBkZXBlbmRpbmcgdXBvbiBob3cgdGhpbmdzIHNoYWtlIG91dC4gQWxzbywgc29tZSBwYXRob2dlbnMganVzdCB3b24ndCBoYXZlIGEgc3BlY2lmaWMgY29uY2VwdCBhc3NvY2lhdGVkIHdpdGggaXQgYXQgYWxsIChsaWtlIHJlc2Vydm9pciwgd2hpY2ggb25seSBvY2N1cnMgaW4gMyBwYXRob2dlbnM7IHRoaXMgZG9lbnMndCBtZWFuIG9ubHkgdGhyZWUgcGF0aG9nZW5zIGhhdmUgcmVzZXJ2b2lycywgYnV0IHRoYXQgb25seSB0aHJlZSBwYXRob2dlbnMgaGF2ZSB0aG9zZSBiaWdyYW0gdGVybXMgYXNzb2NpYXRlZCB3aXRoIGF0IGFuZCB0aGVyZSBpcyBzb21lIHNpZ25hbCBpbiB0aGUgZGF0YSB0byBwaWNrIGl0IHVwKQoKRm9sbG93aW5nIHRoZSBpbml0aWFsIHNhbXBsaW5nIEkgd2lsbCBtYW51YWxseSByZXZpZXcgZWFjaCBvZiB0aGUgc3VnZ2VzdGVkIGRvY3VtZW50cyB0byBzZWUgaWYgdGhleSBjb250YWluIGEgdmlzdWFsaXphdGlvbiB0aGF0IGlzIHJlbGV2YW50IChmb3IgZXhhbXBsZSBzb21lIHBhcGVycyBtYXkganVzdCBoYXZlIHBpY3R1cmVzIG9mIGdlbHMpLiBJZiB0aGVyZSBhcmUgbm8gdmlzdWFsaXphdGlvbnMsIG9yIHRoZSB2aXN1YWxpemF0aW9ucyBhcmUgbm90IHJlbGV2YW50LCBJIHdpbGwgcmVzYW1wbGUgZG9jdW1lbnRzIChmb3IgYSBnaXZlbiBwYXRob2dlbikgdW50aWwgSSBvbmNlIGFnYWluIGhhdmUgY292ZXJhZ2Ugb2YgYWxsIGRvY3VtZW50cyBhbmQgcmVsZXZhbnQgZGF0YSB2aXN1YWxpemF0aW9ucy4gQWxzbywgaWYgaXQgc2VlbXMgbGlrZSBhIGRvY3VtZW50IGhhcyBhIHZpc3VhbGl6YXRpb24gSSd2ZSBhbHJlYWR5IHNlZW4gKEkgZXhwZWN0IG1hbnkgcGh5bG9nZW5ldGljIHRyZWVzIHdpdGhvdXQgYW5ub3RhdGlvbnMgKG5vdGUsIHBoeWxvZ2VuZXRpYyB0cmVlcyB3aXRoIGRpZmZlcmVudHMga2luZHMgb2YgYW5ub3RhdGlvbnMgYXJlIGludGVyZXN0aW5nIGFuZCBjb3VudCBhcyBkaWZmZXJlbnQgdGhpbmdzKSkgSSB3aWxsIGFsc28gcmVzYW1wbGUgdG8gdHJ5IGdldCBzb21lIGRpdmVyc2l0eS4gSSB3aWxsIGtlZXAgYSByZWNvcmQgZm9yIHRoZSBkZWNpc2lvbnMgdGhhdCB3ZXJlIG1hZGUgYWxvbmcgdGhlIHdheS4gCgoKIyMgU2FtcGxpbmcgcm91bmQgMQpgYGB7ciBzYW1wbGluZ0RvY3VtZW50cywgbWVzc2FnZT1GLHdhcm5pbmc9Rn0Kc2V0LnNlZWQoNDE2KQoKZG9jU3RvcmU8LWMoKQoKI3NvcnQgZnJvbSBwYXRob2dlbiB3aXRoIGxlYXN0IHBhcGVycyB0byBtb3N0LCBzdGFydCB3aXRoIGxlYXN0IHBhcGVycy4KY29uY2VwdHM8LWRhdCAlPiUKICBkcGx5cjo6c2VsZWN0KFBNSUQsUGF0aG9nZW4sd2h5KSU+JQogIGRpc3RpbmN0KCklPiUKICBtdXRhdGUoUGF0aG9nZW4gPSBmYWN0b3IoaWZlbHNlKFBhdGhvZ2VuICVpbiUgdG90YWxfZG9jcyRQYXRob2dlbixQYXRob2dlbiwgIk90aGVyIiksbGV2ZWxzPWModG90YWxfZG9jcyRQYXRob2dlbiwiT3RoZXIiKSkpICU+JQogIGdyb3VwX2J5KHdoeSkgJT4lCiAgY291bnQoKSAlPiUKICBhcnJhbmdlKG4pCgoKZm9yKGNvbmNlcHQgaW4gY29uY2VwdHMkd2h5KXsKICAjc2VsZWN0IGRvY3VtZW50cwogIGRvY1NhbXBsZTwtZGF0ICU+JQogICAgZHBseXI6OnNlbGVjdChQTUlELHdoeSxQYXRob2dlbikgJT4lCiAgICBtdXRhdGUoUGF0aG9nZW4gPSBmYWN0b3IoaWZlbHNlKFBhdGhvZ2VuICVpbiUgdG90YWxfZG9jcyRQYXRob2dlbixQYXRob2dlbiwgIk90aGVyIiksbGV2ZWxzPWModG90YWxfZG9jcyRQYXRob2dlbiwiT3RoZXIiKSkpICU+JQogICAgZGlzdGluY3QoKSU+JQogICAgZmlsdGVyKHdoeSA9PSBjb25jZXB0KSAlPiUgCiAgICBncm91cF9ieShQYXRob2dlbiklPiUKICAgIHNhbXBsZV9uKDEpCiAgCiAKICBkb2NTdG9yZTwtcmJpbmQoZG9jU3RvcmUsZG9jU2FtcGxlKQp9CgpnZW5FcGlEYXRhIDwtIHJlYWRSRFMoZmlsZT0iZGF0YS9nZW5FcGlEYXRhX2luaXRpYWxRdWVyeTIuUkRTIikKCiMyNzYgZG9jdW1lbnRzCmdlbkVwaURhdGE8LWdlbkVwaURhdGEgJT4lCiAgZmlsdGVyKFBNSUQgJWluJSByZXBsYWNlbWVudCRQTUlEKQoKI2ZvciBlYWNoIFBNSUQsIEkgd2lsbCBzdW1tYXJpemUgdGhlIGNvbmNlcHQgdGFncyB0aGF0IGFyZSB3aXRoaW4gZWFjaCBkb2N1bWVudHMKZ2VuRXBpRGF0YSRjb25jZXB0czwtcmVwKE5BLG5yb3coZ2VuRXBpRGF0YSkpCmdlbkVwaURhdGEkUGF0aG9nZW48LXJlcChOQSxucm93KGdlbkVwaURhdGEpKQpnZW5FcGlEYXRhJHJlcGxhY2VtZW50UE1JRDwtcmVwKE5BLG5yb3coZ2VuRXBpRGF0YSkpCgpjb25jZXB0Q292ZXJhZ2U8LWMoKQpwYXRob2dlbkNvdmVyYWdlPC1jKCkKCmZvcihpIGluIDE6bnJvdyhnZW5FcGlEYXRhKSl7CiAgUE1JRHZhbDwtZ2VuRXBpRGF0YVtpLCJQTUlEIl0KICAKICBjb25jZXB0VGFnczwtZmlsdGVyKGRhdFN0b3JlLFBNSUQgPT0gUE1JRHZhbCkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KHdoeSxQYXRob2dlbikgJT4lCiAgICBkaXN0aW5jdCgpCiAgCiAgY29uY2VwdENvdmVyYWdlPC1jKGNvbmNlcHRDb3ZlcmFnZSx1bmlxdWUoY29uY2VwdFRhZ3Mkd2h5KSkKICBwYXRob2dlbkNvdmVyYWdlPC1jKHBhdGhvZ2VuQ292ZXJhZ2UsdW5pcXVlKGNvbmNlcHRUYWdzJFBhdGhvZ2VuKSkKICAKICBnZW5FcGlEYXRhW2ksImNvbmNlcHRzIl08LXBhc3RlMCh1bmlxdWUoY29uY2VwdFRhZ3Mkd2h5KSxjb2xsYXBzZT0iOyIpCiAgZ2VuRXBpRGF0YVtpLCJQYXRob2dlbiJdPC1wYXN0ZTAodW5pcXVlKGNvbmNlcHRUYWdzJFBhdGhvZ2VuKSxjb2xsYXBzZT0iOyIpCn0KClZpZXcoZHBseXI6OnNlbGVjdChnZW5FcGlEYXRhLC1jb250YWlucygibWVzaFRlcm1zIikpKQogCiN3cml0ZS5jc3YoZmlsZT0iZGF0YS9kb2N1bWVudFNhbXBsZV92MC4xLjFjc3YiLGRwbHlyOjpzZWxlY3QoZ2VuRXBpRGF0YSwtY29udGFpbnMoIm1lc2hUZXJtcyIpKSxxdW90ZT1UKQoKI2ZpbmFsbHksIHRoZSBjb25jZXB0IGNvdmVyYWdlIGJ5IHRoZXNlIDI3NiBhcnRpY2xlcwoKc29ydCh0YWJsZShjb25jZXB0Q292ZXJhZ2UpKQoKc29ydCh0YWJsZShwYXRob2dlbkNvdmVyYWdlKSkKYGBgCgoqTm90ZSB0byBmdXR1cmUgc2VsZjoqClZlcnNpb24gMS4wIG9mIHRoZSBzYW1wbGluZyB3YXMgbWUgdHJ5aW5nIHRoaW5ncyBvdXQsIGlmIHNvbWVvbmUgcmFuIHRocm91Z2ggdGhpcyBhbmFseXNpcywgdGhlIG1pZ2h0IG5vdCBnZXQgdGhlIGRvY3VtZW50cyBpbiB2ZXJzaW9uIDEuMC4KClZlcmlvbiAxLjEgKHRoZSB2ZXJzaW9uIHVzZWQgaW4gYW5hbHlzaXMpIHdhcyBvYnRhaW5lZCB3aGVuIEkgcmFuIHRocm91Z2ggdGhlIGVudGlyZSBhbmFseXNpcyBhcyBpcywgbm90IGV4dHJhIGFuYWx5c2lzIG9yIGZpZ3VyaW5nIHRoaW5ncyBvdXQuIEluIHRoZW9yeSwgc29tZW9uZSBydW5uaW5nIHRocm91Z2ggdGhpcyBhbmFseXNpcyBzaG91bGQgZW5kIHVwIHdpdGggdGhlIHNhbWUgc2FtcGxlZCBkb2N1bWVudHMgKHNpbmNlIEkgc2V0IGEgc2VlZCkgYXMgYXJ0aWNsZXMuCgoKKipSZXZpZXcgb2YgU2FtcGxlZCBEb2N1bWVudHMqKgoKSSBoYXZlIG5vdyBhbmFseXplZCB0aGUgMjc2IGRvY3VtZW50cyBhbmQgY2xhc3NpZmllZCB0aGVtIGFzIFkgKGFjY2VwdGFibGUgZm9yIGZ1cnRoZXIgYW5hbHlzaXMpLCBOIChub3QgYWNjZXB0YWJsZSBmb3IgZnVydGhlciBhbmFseXNpcyksIG9yIE0gKEkndmUgaW5jbHVkZWQgaXQgZm9yIG5vdywgYnV0IEkgd2FzIG9uIHRoZSBmZW5jZSBhYm91dCBpdCkuCgpgYGB7ciBzYW1wRG9jQW5hbHlzaXMyLCB3YXJuaW5nPUYsbWVzc2FnZT1GfQoKc2FtcERvYzwtcmVhZC5jc3YoZmlsZT0iLi4vZGF0YS9kb2N1bWVudFNhbXBsZV92MC4xLjFfQ1NWLmNzdiIsaGVhZGVyPVQsc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojY2xlYW4gdXAgYW55IHdoaXRlIHNwYWNlCnNhbXBEb2MkSW5jbHVkZTwtdHJpbXdzKHNhbXApCgp0YWJsZShzYW1wRG9jJEluY2x1ZGUpCmBgYApTbyBmYXIsIEkgaGF2ZSBpbmNsdWRlZCBvbmx5IDEwNyBkb2N1bWVudHMsIGEgc3VjY2VzcyByYXRlIG9mIH40MCUsIG9yIG9ubHkgMzIuNiUgZm9yIGFydGljbGVzIHRoYXQgd2VyZSBjb25maWRlbnRseSBpbmNsdWRlZC4gVGhlIG90aGVyIGFydGljbGVzIGhhdmUgYmVlbiBleGNsdWRlZCBmb3IgdmFyaW91cyByZWFzb25zIHRoYXQgYXJlIGluZGljYXRlZCBpbiB0aGUgZXhjZWwgZG9jdW1lbnQuCgpJdCdzIGFsc28gcG9zc2libGUgdG8gc2VlIGluY2x1c2lvbiBieSB5ZWFyOgpgYGB7ciBzYW1wRG9jQW5hbHlzaXMsIHdhcm5pbmc9RixtZXNzYWdlPUZ9CnRhYmxlKHNhbXBEb2MkWWVhclB1YixzYW1wRG9jJEluY2x1ZGUuLlkuTi4pCmBgYApBcnRpY2xlcyBpbiAyMDEwIGFuZCBiZXlvbmQgd2VyZSBtb3JlIGxpa2VseSB0byBiZSBpbmNsdWRlZC4gRm9yIHJlc2FtcGxpbmcgZWZmb3J0cyBpdCBtaWdodCBiZSBtb3JlIGZydWl0ZnVsIHRvIHNhbXBsZSBsYXRlciB5ZWFycywgcmF0aGVyIHRoYW4gc2FtcGxlIHB1YmxpY2F0aW9ucyBmcm9tIG11Y2ggZWFybGllci4gCgpGaW5hbGx5LCB3ZSBjYW4gYWxzbyBzZWUgdGhlIGNvbmNlcHRzIGNvdmVyZWQgYnkgdGhlIHNhbXBsZWQgZG9jdW1lbnRzCmBgYHtyIHRvcGljc0NvdmVyZWRCeUFscmVhZHlTYW1wbGVkRG9jdW1lbnRzLCBtZXNzYWdlPUYsd2FybmluZz1GfQojY29uY2VwdCBwYXRoIGNvdmVycyB0aGUgdG9waWNzIGNvdmVyZWQgaW4gdGhlIGFjY2VwdGVkIGRvY3VtZW50CmNvbmNlcHRwYXRoPC1zYW1wRG9jICU+JQogIGZpbHRlcihJbmNsdWRlID09ICJZIikgJT4lCiAgbXV0YXRlKGNvbmNlcHRzID0gaWZlbHNlKHJldmlzZWQuY29uY2VwdHM9PSIiLGNvbmNlcHRzLHJldmlzZWQuY29uY2VwdHMpKSU+JSAKICBtdXRhdGUoY29uY2VwdHMgPSBzdHJzcGxpdChhcy5jaGFyYWN0ZXIoY29uY2VwdHMpLCAiOyIpKSAlPiUKICB0aWR5cjo6dW5uZXN0KCkgJT4lCiAgI2ZpeCBhIHR5cG8KICBtdXRhdGUoY29uY2VwdHMgPSBpZmVsc2UoY29uY2VwdHMgPT0gInN1cnZlaWxsZW5jZSIsInN1cnZlaWxsYW5jZSIsdHJpbXdzKGNvbmNlcHRzKSkpICU+JQogIG11dGF0ZShQYXRob2dlbiA9IHN0cnNwbGl0KGFzLmNoYXJhY3RlcihQYXRob2dlbiksICI7IikpICU+JQogIHRpZHlyOjp1bm5lc3QoKSAlPiUKICB1bmdyb3VwKCklPiUKICBncm91cF9ieShQYXRob2dlbixjb25jZXB0cyklPiUKICBjb3VudCgpICU+JQogIHVuZ3JvdXAoKSU+JQogIHRpZHlyOjpjb21wbGV0ZShQYXRob2dlbiwgY29uY2VwdHMsIGZpbGwgPSBsaXN0KG4gPSAwKSkKCnRtcE9yZGVyQ29uY2VwdDwtIGNvbmNlcHRwYXRoICU+JSB1bmdyb3VwKCkgJT4lIGdyb3VwX2J5KGNvbmNlcHRzKSAlPiUgc3VtbWFyaXNlKG5uPXN1bShuKSkgJT4lIGFycmFuZ2UoLW5uKQp0bXBPcmRlclBhdGhvZ2VuPC0gY29uY2VwdHBhdGggJT4lIHVuZ3JvdXAoKSAlPiUgZ3JvdXBfYnkoUGF0aG9nZW4pICU+JSBzdW1tYXJpc2Uobm49c3VtKG4pKSAlPiUgYXJyYW5nZSgtbm4pCgpjb25jZXB0cGF0aCRjb25jZXB0czwtZmFjdG9yKGNvbmNlcHRwYXRoJGNvbmNlcHRzLGxldmVscz10bXBPcmRlckNvbmNlcHQkY29uY2VwdHMpCmNvbmNlcHRwYXRoJFBhdGhvZ2VuPC1mYWN0b3IoY29uY2VwdHBhdGgkUGF0aG9nZW4sbGV2ZWxzPXRtcE9yZGVyUGF0aG9nZW4kUGF0aG9nZW4pCgpnZ3Bsb3QoZGF0YT1jb25jZXB0cGF0aCxhZXMoeT1jb25jZXB0cyx4PVBhdGhvZ2VuKSkrCiAgZ2VvbV90aWxlKGFlcyhmaWxsPW4pLGNvbG91cj0id2hpdGUiKSsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iI2ZmZmZmZiIsaGlnaD0iIzAwMDAwMCIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsaGp1c3Q9MSx2anVzdD0wLjUpKQpgYGAKCiMjIFNhbXBsaW5nIHJvdW5kIDIKVGhlIGdvYWwgb2YgcmVzYW1wbGluZyBpcyB0byByZXBsYWNlIHBhdGhvZ2VucyBhbmQgY29uY2VwdHMgdGhhdCB3ZSBjbGFzc2lmaWVkIGFzIE4gb3IgTSBpbiB0aGUgZmlyc3Qgcm91bmQgb2YgcmV2aWV3cy4gSXRlbXMgd2VyZSBjbGFzc2lmaWVkIGFzIE4gaWYgdGhleSBjb250YWluZWQgbm8gZGF0YSB2aXN1YWxpemF0aW9ucywgb3Igd2VyZSBvZmYgdG9waWMsIG9yIGRpZG4ndCByZWFsbHkgdGFsayBhYm91dCBodW1hbnMgKHdpdGggc29tZSB2ZXJ5IGZldyBleGNlcHRpb25zKS4gVGhlIHZhcmlvdXMgcmVhc29ucyBhcmUgbGlzdGVkIGluIHRoZSBleGNlbCBkb2N1bWVudC4gTm90ZSB0aGF0IGp1c3QgbGFiIGltYWdlcyAocGZnZSBnZWxzLCBtaWNyb3Njb3B5IGltYWdlcykgd2VyZSBub3QgY29uc2lkZXJlZCB0byBiZSBkYXRhIHZpc3VhbGl6YXRpb25zLiBTb21lIHBhcGVycyBoYWQgcGZnZSBnZWxzICsgYSBwaHlsb2dlbmV0aWMgdHJlZSBmb3IgYW5ub3RhdGlvbiwgYW5kIHRoYXQgKndhcyogY29uc2lkZXJlZCB0byBiZSBhIGRhdGEgdmlzdWFsaXphdGlvbi4gCgpUaGUgc2FtcGxpbmcgaGVyZSB3aWxsIGdvIGFzIGZvbGxvd3M6IHR3byBhdHRlbXB0cyB3aWxsIGJlIG1hZGUgdG8gcmVzYW1wbGUgZWFjaCBwYXRob2dlbiBhbmQgY29uY2VwdCBjb21iaW5hdGlvbiwgc2luY2UgbW9zdCBwYXBlcnMgY292ZXIgbXVsdGlwbGUgdG9waWNzIHByaW9ydHkgaXMgZ2l2ZW4gdG8gYSB0b3BpYyB0aGF0IGlzbid0IHlldCBjb252ZXJlZCBieSB0aGF0IHBhdGhvZ2VuIGFuZCBhZnRlcndhcmRzLCBhIHRvcGljIHRoYXQgaXMgbm90IHdlbGwgY292ZXJlZCBpbiBnZW5lcmFsLiBUaGUgZmlyc3Qgb2YgdGhlIHJlc2FtcGxlZCBwYXBlcnMgdGhhdCBnZXRzIGEgWSBpcyBhY2NlcHRlZCAoaWYgdGhlIGZpcnN0IG9mIHR3byBpcyBhIGdvb2QgaGl0LCB0aGVuIHRoZSBzZWNvbmQgc2FtcGxlIGlzIGRpc2NhcmRlZCkuIAoKSSBub3RpY2VkIHdoaWxlIGdvaW5nIHRocm91Z2ggdGhlIGZpcnN0IGJhdGNoIG9mIDI3NiBwYXBlcnMgdGhhdCBtYW55IG9sZGVyIHBhcGVycyB3ZXJlIGhhcmRlciB0byBhY2Nlc3Mgb3IgY29udGFpbmVkIGZld2VyIGRhdGEgdmlzdWFsaXphdGlvbnMuIFNvIHRoaXMgbmV4dCBiYXRjaCBvZiBzYW1wbGVzIHdpbGwgYmUgbGltaXRlZCBwYXBlcnMgcHVibGlzaGVkIGluIDIwMTEgYW5kIGJleW9uZC4gVGhpcyBzaG91bGQgaG9wZWZ1bCBzYW1wbGUgc29tZSBwYXBlcnMgdGhhdCBhcmVuJ3QganVzdCBwaHlsb2dlbmV0aWMgdHJlZXMgd2l0aCBhbm5vdGF0aW5nIHRleHQuIAoKYGBge3IgcmVzYW1wbGluZywgbWVzc2FnZT1GLHdhcm5pbmc9Rn0Kc2V0LnNlZWQoNDMwKQoKY29uY2VwdHNJbml0aWFsPC1jb25jZXB0cGF0aAoKI3JlbW92ZSBkb2N1bWVudHMgYWxyZWFkeSBzYW1wbGVkCmlkeDIwMTE8LWZpbHRlcihnZW5FcGlEYXRhLFllYXJQdWI+PTIwMTEpICU+JSBkcGx5cjo6c2VsZWN0KFBNSUQpCmRhdFN1YiA8LSBmaWx0ZXIoZGF0LCAhKFBNSUQgJWluJSBzYW1wRG9jJFBNSUQpKSAlPiUgZmlsdGVyKFBNSUQgJWluJSBpZHgyMDExJFBNSUQpCgojcmVwbGFjZSBhIHByZXZpb3VzIHBhcGVyIHRoYXQgd2FzIHJlamVjdGVkIChOKQpyZWplY3RlZFBNSUQ8LWZpbHRlcihzYW1wRG9jLCBJbmNsdWRlID09ICJOIikgJT4lIGRwbHlyOjpzZWxlY3QoUE1JRCkKCnJlcGxhY2VtZW50ID0gYygpCgpmb3IocmVqZWN0ZWRQYXBlciBpbiByZWplY3RlZFBNSUQkUE1JRCl7CiAgdG1wPC1kYXQgJT4lIGZpbHRlcihQTUlEID09IHJlamVjdGVkUGFwZXIpICU+JQogICAgZHBseXI6OnNlbGVjdChQYXRob2dlbix3aHkpICU+JSAKICAgIGRpc3RpbmN0KCkKICAgIAogICAgI3doZW4gYSBzaW5nbGUgYXJ0aWNsZSBjb3ZlcnMgbWFueSBtYW55IHBhdGhvZ2VucywKICAgICNzYW1wbGUgdGhlIHBhdGhvZ2VuIHdpdGggdGhlIGxlYXN0IG51bWJlciBvZiB0b3BpY3MgY292ZXJlZC4KICAgICN0aGlzIGRvZXNuJ3QgaGFwcGVuIGEgbG90LCBidXQgc29tZSBhcnRpY2xlcyBhcmUgYWJvdXQgYSBsb3Qgb2YgYnVncwogICAgblBhdGg8LWxlbmd0aCh1bmlxdWUodG1wJFBhdGhvZ2VuKSkKICAgIAogICAgaWYoblBhdGggPiAxKXsKICAgICAgcGF0aFNhbXAgPC0gIHRtcE9yZGVyUGF0aG9nZW4kUGF0aG9nZW5bbWF4KHdoaWNoKHRtcE9yZGVyUGF0aG9nZW4kUGF0aG9nZW4gJWluJSB1bmlxdWUodG1wJFBhdGhvZ2VuKSkpXQogICAgfWVsc2V7CiAgICAgIHBhdGhTYW1wIDwtIHVuaXF1ZSh0bXAkUGF0aG9nZW4pCiAgICB9CiAgICAKICAgICNmb3IgdGhhdCBwYXRob2dlbiwgc29ydCBjb25jZXB0cyBhY2NvcmRpbmcgdG8gY292ZXJhZ2UKICAgIHRvcGljT3JkZXI8LWZpbHRlcihjb25jZXB0cGF0aCxQYXRob2dlbiA9PSBwYXRoU2FtcCkgJT4lCiAgICAgIGFycmFuZ2UobikgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgZ3JvdXBfYnkobiklPiUKICAgICAgc2FtcGxlX2ZyYWMoMSkjdGhpcyByYW5kb21seSBvcmRlcnMgaXRlbXMgd2l0aGluIHRoZSBzYW1lIGNvdW50IGxldmVsIChzZWUgcmF0aW9uYWxlIGJlbG93KQogICAgCiAgICAjIHNvbWUgYnVncyB0aGF0IHdlcmUgc2FtcGxlZCBoYWQgKm5vKiBwcmV2aW91cyBkb2N1bWVudHMgc2FtcGxlZCAoY29uY2VwdHBhdGgKICAgICMgY29udGFpbnMgc2FtcGxlZCBkb2N1bWVudHMgdGhhdCB3ZXJlICpub3QqIHJlamVjdGVkLikuIFRoaXMgY2FuIGJlIHRoZSBjYXNlIGZvcgogICAgIyBidWdzIGxpa2UgWmlrYSBhbmQgRWJvbGEgd2hpY2ggYXJlIG5ld2VyLCBhbmQgZ290IGluIHRoYXQgIm90aGVyIiBjYXRlZ29yeS4gU28KICAgICMgcGljayBhbnkgY29uY2VwdCB0byBzYW1wbGUKICAgIGlmKG5yb3codG9waWNPcmRlcik9PTApewogICAgICB0b3BpY09yZGVyPC1kYXRhLmZyYW1lKGNvbmNlcHRzID1sZXZlbHMoY29uY2VwdHBhdGgkY29uY2VwdHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSByZXAoMCxsZW5ndGgobGV2ZWxzKGNvbmNlcHRwYXRoJGNvbmNlcHRzKSkpKQogICAgfQogICAgCiAgICBob2xkaW5nPC0gYygpICNrZWVwaW5nIHRyYWNrIG9mIHdoYXQncyBiZWVuIHNhbXBsZWQsIGFnYWluLCBvbmx5IG5lZWQgdHdvIGRvY3VtZW50cwogICAgCiAgICAjIHNhbXBsZSB0aGUgbGVhc3Qgc2FtcGxlZCB0b3BpY3MgZmlyc3QsIGFuZCB3b3JrIHVwIHRoZSBsaXN0CiAgICAjIHJhbmRvbWx5IG9yZGVyIHRvcGljcyB3aXRoaW5nIHRoZSBzYW1lIGNvdW50IChzZWUgYWJvdmUpCiAgICAjIHNvbWV0aW1lcyAiaG9sZGluZyIgaGFzIGEgUE1JRCBzdG9yZWQgd2hpY2ggaGFzIG5vdCB5ZXQgYmVlbiBwdXNoZWQKICAgICMgdG8gcmVwbGFjZW1lbnQsIHRoaXMgY2FuIGNhdXNlIHNvbWUgZHVwbGljYXRlZCBQTUlEcywgYW5kIEkndmUgZGVjaWRlZAogICAgIyB0aGF0J3MgZmluZSwgc2luY2UgdGhlIG51bWJlciBvZiByZXBsYWNhdGVkIElEcyBpcyBmZXcgYW5kIHRoZXJlIGFyZQogICAgIyAicmFyZSIgY29uY2VwdHMvcGF0aG9nZW4gY29tYmluYXRpb25zIHdoaWNoIG1heSBvbmx5IGVuZCB1cCB3aXRoIG9uZSBhcnRpY2xlIGFueXdheQogICAgZm9yKHRvcGljIGluIHRvcGljT3JkZXIkY29uY2VwdHMpewogICAgICB0bXAyPC0gZmlsdGVyKGRhdFN1YiwgUGF0aG9nZW4gPT0gcGF0aFNhbXAgJiB3aHkgPT0gdG9waWMpCiAgICAgIAogICAgICAjdHJ5IHRvIHNhbXBsZSBhcyBtYW55IHVuaXF1ZSBhcnRpY2xlcyBhcyBwb3NzaWJsZQogICAgICBpZighKGlzLm51bGwocmVwbGFjZW1lbnQpKSAmIG5yb3codG1wMik+MSl7CiAgICAgICAgdG1wMjwtZmlsdGVyKHRtcDIsIShQTUlEICVpbiUgcmVwbGFjZW1lbnQkUE1JRCkpCiAgICAgIH0KCiAgICAgIGlmKG5yb3codG1wMik9PTApewogICAgICAgICNtb3ZlIG9uIHRvIHRoZSBuZXh0IHRvcGljLCB0aGVyZSdzIG5vdGhpbmcgdG8gc2VlIGhlcmUKICAgICAgICBuZXh0OwogICAgICB9ZWxzZSBpZihucm93KHRtcDIpPT0xKXsKICAgICAgICAjaWYgdGhlcmUncyBvbmx5IG9uZSBwYXBlciB0byBwaWNrIGZyb20sIHNlbGVjdCBpdCB0aGVuIG1vdmUgb24gdG8gdGhlIG5leHQgdG9waWMKICAgICAgICB0bXAyPC1jYmluZCh0bXAyLHJlamVjdGVkUGFwZXIpCiAgICAgICAgbmFtZXModG1wMik8LWMobmFtZXModG1wMilbMToobmNvbCh0bXAyKS0xKV0sInJlamVjdGVkUE1JRCIpCiAgICAgICAgaG9sZGluZzwtcmJpbmQoaG9sZGluZyx0bXAyKQogICAgICB9ZWxzZSBpZihucm93KHRtcDIpPjEpewogICAgICAgIGlmKGlzLm51bGwobnJvdyhob2xkaW5nKSkpewogICAgICAgICAgICNpZiB0aGVyZSBhcmUgbW9yZSB0aGFuIHR3byBwYXBlcnMgdG8gcGljayBmcm9tLCByYW5kb21seSBzYW1wbGUgMgogICAgICAgICAgICB0bXAyPC1jYmluZChkcGx5cjo6c2FtcGxlX24odG1wMiwyKSxyZXAocmVqZWN0ZWRQYXBlciwyKSkKICAgICAgICB9ZWxzZSBpZihucm93KGhvbGRpbmcpPT0xKXsKICAgICAgICAgIHRtcDI8LWNiaW5kKGRwbHlyOjpzYW1wbGVfbih0bXAyLDEpLHJlamVjdGVkUGFwZXIpCiAgICAgICAgfQogICAgICAgCiAgICAgICAgbmFtZXModG1wMik8LWMobmFtZXModG1wMilbMToobmNvbCh0bXAyKS0xKV0sInJlamVjdGVkUE1JRCIpCiAgICAgICAgaG9sZGluZzwtcmJpbmQoaG9sZGluZyx0bXAyKQogICAgICB9CiAgICAgIAogICAgICBpZihucm93KGhvbGRpbmcpID09IDIpewogICAgICAgICN1cGRhdGUgdGhlIHJlcGxhY2VtZW50IG9iamVjdAogICAgICAgIHJlcGxhY2VtZW50PC1yYmluZChyZXBsYWNlbWVudCxob2xkaW5nKQogICAgICAgIAogICAgICAgICN1cGRhdGUgdGhlIGNvbmNlcHQgY291bnQgb3B0aW9uIC0gdGhlIGxhenkgd2F5CiAgICAgICAgI25vdGUgdG8gc2VsZiAtIEkgY2hlY2tlZCBhbmQgdGhpcyB3b3JrcyB3b28hCiAgICAgICAgI3NvIG5leHQgdGltZSBhIHBhdGhvZ2VuIG5lZWRzIHRvIHJlcGxhY2UgcGFwZXIgd2UgZ2V0IHVuZGVyc2FtcGxlZCB0b3BpY3MhCiAgICAgICAgZm9yKGkgaW4gMToyKXsKICAgICAgICAgIHBhdGhTYW1wPC1ob2xkaW5nW2ksIlBhdGhvZ2VuIl0KICAgICAgICAgIHRvcGljPC1ob2xkaW5nW2ksIndoeSJdCiAgICAgICAgICAKICAgICAgICAgIGNvbmNlcHRwYXRoPC1jb25jZXB0cGF0aCAlPiUKICAgICAgICAgICAgdW5ncm91cCgpJT4lCiAgICAgICAgICAgIG11dGF0ZShuPWlmZWxzZShQYXRob2dlbiA9PSBwYXRoU2FtcCAmIGNvbmNlcHRzID09IHRvcGljLCBuICsgMSxuKSkKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgaG9sZGluZzwtYygpCiAgICAgICAgYnJlYWs7CiAgICAgIH1lbHNlewogICAgICAgIG5leHQ7CiAgICAgIH0KICAgIH0KfQoKcDE8LWdncGxvdChkYXRhPWNvbmNlcHRzSW5pdGlhbCxhZXMoeT1jb25jZXB0cyx4PVBhdGhvZ2VuKSkrCiAgZ2VvbV90aWxlKGFlcyhmaWxsPW4pLGNvbG91cj0id2hpdGUiKSsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iI2ZmZmZmZiIsaGlnaD0iIzAwMDAwMCIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIixheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCxoanVzdD0xLHZqdXN0PTAuNSkpCgpwMjwtZ2dwbG90KGRhdGE9Y29uY2VwdHBhdGgsYWVzKHk9Y29uY2VwdHMseD1QYXRob2dlbikpKwogIGdlb21fdGlsZShhZXMoZmlsbD1uKSxjb2xvdXI9IndoaXRlIikrCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IiNmZmZmZmYiLGhpZ2g9IiMwMDAwMDAiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsaGp1c3Q9MSx2anVzdD0wLjUpKQoKY293cGxvdDo6cGxvdF9ncmlkKHAxLHAyKQpgYGAKCgpCZWxvdywgaGlnaGxpZ2h0IHRoZSBhcmVhcyB3aGVyZSB0aGUgc2FtcGxpbmcgc2NoZW1lIHRyaWVkIHRvIGJvb3N0IHNpZ25hbApgYGB7ciBwbG90Q29udGludWVkLCBtZXNzYWdlPUYsd2FybmluZz1GfQpkaWZmQ29uY2VwdDwtY29uY2VwdHNJbml0aWFsICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUobmV3Q291bnRzID0gY29uY2VwdHBhdGgkbikgJT4lCiAgbXV0YXRlKGRpZmYgPSBuZXdDb3VudHMgLSBuKQoKZ2dwbG90KGRhdGE9ZGlmZkNvbmNlcHQsYWVzKHk9Y29uY2VwdHMseD1QYXRob2dlbikpKwogIGdlb21fdGlsZShhZXMoZmlsbD1kaWZmKSxjb2xvdXI9IndoaXRlIikrCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IiNmZmZmZmYiLGhpZ2g9IiMwMDAwMDAiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLGhqdXN0PTEsdmp1c3Q9MC41KSkKYGBgCgpPaywgdGhlIHRvdGFsIG51bWJlciBvZiByZXNhbXBsZWQgZG9jdW1lbnRzIG1hdGNoZXMgd2hhdCBJIGV4cGVjdCAoMzEwKS4KCmBgYHtyIGFic2VudGNvdW50c30KcmVwbGFjZW1lbnQgJT4lIAogIHVuZ3JvdXAoKSU+JQogIGdyb3VwX2J5KHJlamVjdGVkUE1JRCkgJT4lCiAgY291bnQoKSAlPiUgCiAgZmlsdGVyKG5uIDwgMikgI25vIHRoYXQncyBub3QgaXQKCnNldGRpZmYocmVqZWN0ZWRQTUlEJFBNSUQscmVwbGFjZW1lbnQkcmVqZWN0ZWRQTUlEKSAjb2ssIHNpeCBoYWQgc29tZXRoaW5nIHdlaXJkCgpgYGAKCmBgYHtyIGNvbGxlY3RXaXRoR2VuRXBpLCBtZXNzYWdlPUYsd2FybmluZz1GfQpnZW5FcGlEYXRhPC1nZW5FcGlEYXRhICU+JQogIGZpbHRlcihQTUlEICVpbiUgcmVwbGFjZW1lbnQkUE1JRCkKCiNmb3IgZWFjaCBQTUlELCBJIHdpbGwgc3VtbWFyaXplIHRoZSBjb25jZXB0IHRhZ3MgdGhhdCBhcmUgd2l0aGluIGVhY2ggZG9jdW1lbnQKCmdlbkVwaURhdGEkY29uY2VwdHM8LXJlcChOQSxucm93KGdlbkVwaURhdGEpKQpnZW5FcGlEYXRhJFBhdGhvZ2VuPC1yZXAoTkEsbnJvdyhnZW5FcGlEYXRhKSkKZ2VuRXBpRGF0YSRyZXBsYWNpbmdQTUlEPC1yZXAoTkEsbnJvdyhnZW5FcGlEYXRhKSkKCmZvcihpIGluIDE6bnJvdyhnZW5FcGlEYXRhKSl7CiAgUE1JRHZhbDwtZ2VuRXBpRGF0YVtpLCJQTUlEIl0KICAKICBjb25jZXB0VGFnczwtZmlsdGVyKGRhdFN1YixQTUlEID09IFBNSUR2YWwpICU+JQogICAgZHBseXI6OnNlbGVjdCh3aHksUGF0aG9nZW4pICU+JQogICAgZGlzdGluY3QoKQogIAogICAgcmVwbGFjZW1lbnRQTUlEPC1maWx0ZXIocmVwbGFjZW1lbnQsUE1JRCA9PSBQTUlEdmFsKQogIAogIGdlbkVwaURhdGFbaSwiY29uY2VwdHMiXTwtcGFzdGUwKHVuaXF1ZShjb25jZXB0VGFncyR3aHkpLGNvbGxhcHNlPSI7IikKICBnZW5FcGlEYXRhW2ksIlBhdGhvZ2VuIl08LXBhc3RlMCh1bmlxdWUoY29uY2VwdFRhZ3MkUGF0aG9nZW4pLGNvbGxhcHNlPSI7IikKICBnZW5FcGlEYXRhW2ksInJlcGxhY2luZ1BNSUQiXTwtcGFzdGUwKHVuaXF1ZShyZXBsYWNlbWVudFBNSUQkcmVqZWN0ZWRQTUlEKSxjb2xsYXBzZT0iOyIpCn0KClZpZXcoZHBseXI6OnNlbGVjdChnZW5FcGlEYXRhLC1jb250YWlucygibWVzaFRlcm1zIikpKQogCiN3cml0ZS5jc3YoZmlsZT0iZGF0YS9kb2N1bWVudF9SZXNhbXBsZV92MC4xLjAuY3N2IixkcGx5cjo6c2VsZWN0KGdlbkVwaURhdGEsLWNvbnRhaW5zKCJtZXNoVGVybXMiKSkscXVvdGU9VCkKCmBgYAoK